Kafka 高水位了解过吗?为什么 Kafka 需要 LeaderEpoch?
结论:高水位(High Watermark,HW)是 ISR 中最小的 LEO,也是消费者能读到的上界;LEO(Log End Offset)是副本自己的写入边界。HW 决定"哪些消息算已提交"。但只用 HW 做日志截断,在 Leader 切换时会出现"截断过多丢数据"或"截断过少留分叉",所以 Kafka 引入 LeaderEpoch(领导者纪元),让副本按纪元精确截断。
一、HW、LEO 与 LSO
分区日志(offset 递增):
0 1 2 3 4 5 6 7
├────┼────┼────┼────┼────┼────┼────┼────>
▲ ▲ ▲
HW=4 LSO=6 LEO=8
HW = min(ISR 各副本 LEO) 消费者最多读到 4(已充分复制)
LSO = 未完成事务之前的位移 read_committed 最多读到 6
LEO = 下一条要写入的位置 Leader 与每个 Follower 各自维护
| 指标 | 含义 | 谁能看到 |
|---|---|---|
| LEO | 副本下一条待写位移 | 仅 Broker 内部 |
| HW | ISR 最小 LEO,已提交边界 | 消费者可见上界 |
| LSO | 首个未完成事务的起始位移 | read_committed 消费者上界 |
HW 的推进需要一次额外往返:Follower 拉取数据 → 推进自己的 LEO → 下一次 Fetch 上报 → Leader 更新 HW → 随响应带回。所以"三个副本都写完了"与"消费者能读到"之间存在短暂延迟。
二、只用 HW 截断的问题
时刻 T1:A=Leader(LEO=5, HW=5),B=Follower(LEO=5),ISR={A,B}
时刻 T2:B 宕机。A 写入 offset=5,ISR 只剩 A,A 的 HW 推进到 6
时刻 T3:A 宕机。B 恢复并被选为 Leader(epoch 从 E 变 E+1),
在 offset=5 写入一条不同的消息,B 的 LEO=6
时刻 T4:A 恢复,作为 Follower。若按自己的 HW=6 截断,则不会删除
offset=5 的旧消息 → 同一分区出现两条不同的 offset=5 消息(分叉)
反过来也可能出现"截断过多":A 的 HW 落后于它已经返回给客户端的确认位移(尤其 acks=1),恢复后按 HW 截断,就把已确认的消息删掉了。
两种错误的根源相同:HW 是一个"当前值",不带历史版本信息,副本无法知道"这个位移属于哪个 Leader 任期",也就无法判断该保留还是该截断。
三、LeaderEpoch 如何修复
每个 Leader 上任时 epoch + 1,并记录 <epoch, startOffset>:
epoch=E startOffset=0 (A 第一次当 Leader)
epoch=E+1 startOffset=6 (B 上任,从 6 开始写)
A 恢复后发起 OffsetsForLeaderEpoch(epoch=E):
Leader(B) 返回 epoch=E 的结束位移 = 5
A 截断到 5,删除自己那条分叉的 offset=5 消息 → 达成一致
| 机制 | 旧方案 | LeaderEpoch 方案 |
|---|---|---|
| 截断依据 | 自己的 HW | Leader 返回的纪元结束位移 |
| 恢复后行为 | 可能少截断(分叉) | 精确截断到纪元边界 |
| 额外状态 | 无 | leader-epoch-checkpoint 文件 |
| 一致性 | 可能两个副本数据不同 | 分叉被消除 |
# 查看纪元和每个纪元的起始位移
kafka-dump-log.sh --files /data/kafka/order-0/leader-epoch-checkpoint \
--offsets-decoder
四、常见追问
- 为什么消费者不能读到 LEO? 因为 LEO 之后的消息可能还没同步到 ISR,Leader 一旦挂掉这些消息会消失;只暴露 HW 才能保证"读到的消息不会回退"。
- HW 什么时候推进? 在 Follower 的 Fetch 请求到达 Leader 时,Leader 计算
min(ISR 副本 LEO)并更新,然后随响应返回给 Follower。 - 有了 LeaderEpoch,HW 还有用吗? 有。HW 决定消费者可见范围,LeaderEpoch 只解决副本截断的一致性,两者职责不同。
- LSO 和 HW 什么关系? LSO ≤ HW,事务未完成时 LSO 停在事务起始位移,
read_committed消费者读到 LSO 为止,避免读到会被回滚的数据。 - 纪元文件丢了会怎样? 副本会退回 HW 截断逻辑,可能出现分叉,所以
leader-epoch-checkpoint与日志目录必须一起备份、一起迁移。
小结
- HW = ISR 最小 LEO,是"已提交"与"消费者可见"的边界;LEO 是写入边界。
- 只用 HW 截断缺少任期信息,Leader 切换后可能丢数据或留下分叉。
- LeaderEpoch 记录
<epoch, startOffset>,让 Follower 精确截断到纪元边界。 - HW、LSO、LeaderEpoch 三者分别解决可见性、事务可见性与副本一致性。