bigywater.tech
Kafka 内部原理 · 10

High Watermark 和 Log End Offset 的区别

LEO(Log End Offset)是一个副本自己的日志写到了哪;HW(High Watermark)是这个分区对外(消费者)承诺"读到这里都是安全的"的位置。这次用 kill -STOP 暂停一个 Follower(进程活着,只是不干活),观察它暂停期间自己的磁盘数据(LEO)和整个分区对外可见的边界(HW)到底谁先动、谁滞后。

验证:Follower 暂停期间,kafka-log-dirs.sh 查不到它,它重新恢复后立刻追平磁盘大小
暂停期间,kafka-log-dirs.sh 查询的结果里直接说"Received log directory in
验证:即使查询工具因为集群状态异常而完全失效,Producer 的写入也不受影响
同时暂停两个 Follower 之后(KRaft 模式下这个 3 节点集群的 controller 投票也只剩 1/

验证:Follower 暂停期间,kafka-log-dirs.sh 查不到它,它重新恢复后立刻追平磁盘大小

命令 · shell $ kill -STOP 61584 # 暂停 broker1(Follower) $ kafka-log-dirs.sh --bootstrap-server 127.0.0.1:19292 --topic-list hw-leo-demo --describe
真实实测 · 本机执行输出
--- broker1 暂停期间 ---
Querying brokers for log directories information
Received log directory information from brokers 2,3
{"brokers":[{"broker":2,...,"size":27945,...},{"broker":3,...,"size":27945,...}]}

---  kill -CONT 恢复 broker1,等 6 秒后再查 ---
Received log directory information from brokers 1,2,3
{"brokers":[{"broker":1,...,"size":27945,...},{"broker":2,...,"size":27945,...},{"broker":3,...,"size":27945,...}]}

暂停期间,kafka-log-dirs.sh 查询的结果里直接说"Received log directory information from brokers 2,3"——broker1 完全没有响应这次查询,因为它的进程被暂停了,没法处理任何请求,连汇报自己磁盘上有多少数据都做不到。恢复 6 秒后再查,broker1 不仅重新出现在结果里,size 字段还精确等于另外两个 Broker(都是 27945 字节)——说明它在这 6 秒里完成了追赶,本地日志(它自己的 LEO 对应的物理数据)已经和 Leader 一致。这组数据直接证明:一个副本"有多少数据"是它自己的本地状态,暂停时这个状态是不可知的(连查都查不到),不是"暂停期间数据被冻结在某个可读的中间值"。

验证:即使查询工具因为集群状态异常而完全失效,Producer 的写入也不受影响

命令 · shell $ kill -STOP 61584 && kill -STOP 55592 # 同时暂停两个 Follower,只剩 Leader 活着 $ kafka-console-producer.sh --bootstrap-server 127.0.0.1:19292 --topic hw-leo-demo --command-property acks=1 # 发送 500 条 $ kafka-get-offsets.sh --bootstrap-server 127.0.0.1:19292 --topic hw-leo-demo --time -1
真实实测 · 本机执行输出
生产 500 条 acks=1 消息,producer 退出码 = 0(全部成功)

--- 但查询 HW 的命令失败了 ---
Error occurred: org.apache.kafka.common.errors.TimeoutException: Timed out waiting to send the call. Call: listTopics
Caused by: org.apache.kafka.common.errors.DisconnectException: ... due to node 2 being disconnected

同时暂停两个 Follower 之后(KRaft 模式下这个 3 节点集群的 controller 投票也只剩 1/3 存活,凑不齐多数),kafka-console-producer.sh 依然成功发送了全部 500 条消息(acks=1 只需要 Leader 自己确认写入本地日志,不需要凑够 quorum),但 kafka-get-offsets.sh(本质是走 Admin.listTopics(),需要跟 controller 交互)超时失败。这组对照说明 LEO 的推进(数据面,Leader 写自己的本地日志)和 HW/对外可见状态的查询(部分依赖 controller quorum 的管理面操作)是两条独立的路径——数据面能在 controller quorum 失效的情况下继续往前走,管理面查询却完全瘫痪。恢复两个 Follower 之后,重新查询显示 offset 变成了 1100(=之前的 100 + 这次的 500 + 另一次同样场景下成功的 500),证明这些在"查不到 HW"期间写入的数据,最终、完整地追平了。

结论

LEO 是副本自己知道、别人不一定查得到的本地进度;HW 是这个分区愿意告诉消费者的"安全读取边界",依赖于其他副本追上进度(或者被移出 ISR、缩小分母)之后才能推进。实验里最有说服力的部分不是精确量出两者差了多少(这次两次尝试都发现,本地环回网络下副本追赶和 ISR 收缩都在十几秒内完成,窗口太窄,CLI 轮询很难截住"HW 明显落后于 LEO"的中间态),而是证明了这两条路径是独立的:暂停的副本无法汇报自己的 LEO(连查都查不到),即使控制面全部瘫痪,Leader 的数据面写入也在继续推进,最后一致性由的追赶过程兜底。

分步演示:数据面和管理面,是怎么被拆开验证的

每一步都是本文上两节kill -STOP/-CONT、查log-dirs和offset得到的原始结果。

控制面(查HW)瘫痪时,数据面(Leader写LEO)还在继续往前走
这一步新验证的状态

    参考与说明

    • 本文数据来自的 3 节点 Kafka 4.3.1 集群:用 kill -STOP/kill -CONT 暂停/恢复 Follower,用 kafka-log-dirs.sh 查询各 Broker 本地磁盘大小,用 kafka-get-offsets.sh/kafka-console-producer.sh 验证管理面和数据面在 controller quorum 失效时的差异表现。
    • 和《ISR 集合:哪些副本有资格参与选举》一起看:这次单 Follower 暂停、controller quorum 保持健康时,观测到的 ISR 收缩明显比上一篇快(十几秒内 vs 上一篇的 66-97 秒)——印证了上一篇里的怀疑:那次量到的偏大数字,很大一部分来自查询工具本身在集群状态异常时的重试开销,不是 Broker 判断本身变慢了;这是同一套测试手法在不同集群健康状态下测出的、彼此能互相印证的结果。
    • 没有涉及:本文没有测试大量数据(比如几百 MB 的积压)情况下 HW 明显落后于 LEO 的持续可观测窗口——本机小数据量下这个窗口窄到 CLI 轮询很难稳定截住,拉开这个窗口需要更大的数据积压量或者更慢的磁盘/网络环境,留给容量相关的文章。
    京ICP备2026067502号-1京公网安备11010502063487号