揭开 Kafka 水位线的秘密:深度解析 LEO 与 HW 的同步机制
揭开 Kafka 水位线的秘密:深度解析 LEO 与 HW 的同步机制
摘要:在分布式存储中,数据复制是保证高可用的核心。但你是否想过:Follower 是怎么把数据从 Leader 那里“搬”过来的?消费者为什么只能看到一部分数据?HW(高水位)到底是怎么涨上去的?本文将深入 Kafka 的日志复制协议,拆解 LEO 与 HW 的爱恨情仇。
1. 核心概念:什么是 LEO 和 HW?
在深入流程之前,必须先对这两个术语进行精准定义。它们是 Kafka 日志中的两个“游标”。
1.1 LEO (Log End Offset)
- 定义:日志末端位移。它代表下一条消息将被写入的位置。
- 数值:
LEO = 最后一个消息的 Offset + 1。 - 特性:
- 每个副本(Leader 和 Follower)都有自己的 LEO。
- 只要有新消息写入(或同步)成功,LEO 就会
+1。
1.2 HW (High Watermark)
- 定义:高水位线。它定义了消息的可见性和数据的安全边界。
- 数值:所有 ISR(同步副本集合) 中,最小的那个 LEO。
- 公式:
HW = min(LEO_Leader, LEO_Follower1, LEO_Follower2...)(假设都在 ISR 中)。
- 公式:
- 特性:
- 对消费者:消费者只能拉取到
offset < HW的消息。HW 之后的数据对消费者不可见(因为可能还没同步给所有 ISR,随时可能丢失)。 - 对副本:HW 是数据截断(Truncation)的依据。如果 Follower 的数据超过了 HW 但没被确认,重启后会将多出的部分截断。
- 对消费者:消费者只能拉取到
2. 宏观图解:日志结构
假设一个 Partition 有 3 个副本,Offset 0-4 都已同步,Offset 5 刚写入 Leader 但未同步。
3. 微观拆解:同步流程 (Fetch Request)
Kafka 的复制机制是 Pull(拉取) 模式。Follower 主动向 Leader 请求数据。
这个过程最精妙的地方在于:Leader 和 Follower 的 HW 更新是不同步的,通常需要两个 Fetch 请求周期才能完成更新。
我们通过一个场景来演示:Producer 发送了一条消息(Offset 0)。
阶段一:Leader 写入本地
- Producer 发送消息
m1。 - Leader 写入本地 Log。
- Leader 状态:
LEO = 1,HW = 0(因为 Follower 还没拿,最小 LEO 还是 0)。
阶段二:Follower 第一次 Fetch (拉取数据)
- 请求:Follower 发送
FetchRequest(fetch_offset=0)。 - Leader 处理:
- Leader 读取 Log,读到了
m1。 - Leader 更新内存中该 Follower 的
Remote LEO = 0。 - Leader 尝试更新 HW:
min(Leader LEO=1, Remote LEO=0) = 0。HW 保持不变。 - 返回:把
m1数据和Leader HW=0返回给 Follower。
- Leader 读取 Log,读到了
- Follower 处理:
- 写入
m1到本地 Log。 - 更新自己的
LEO = 1。 - 更新自己的 HW:
min(自己的LEO=1, Leader的HW=0) = 0。注意:此时 Follower 虽然有数据了,但 HW 还是 0。
- 写入
阶段三:Follower 第二次 Fetch (确认同步 + 更新 HW)
- 请求:Follower 发送
FetchRequest(fetch_offset=1)。- 潜台词:“我已经有 Offset 0 了,请给我 Offset 1 的数据”。
- Leader 处理:
- 收到
fetch_offset=1,Leader 知道 Follower 已经同步完m1了。 - 更新内存中该 Follower 的
Remote LEO = 1。 - 更新 HW:
min(Leader LEO=1, Remote LEO=1) = 1。Leader 的 HW 更新为 1。 - 返回:没有新数据了(空包),但带回
Leader HW = 1。
- 收到
- Follower 处理:
- 收到空包。
- 更新自己的 HW:
min(自己的LEO=1, Leader的HW=1) = 1。Follower 的 HW 终于更新为 1。
4. 时序图解:两次交互的艺术
5. 存在的缺陷与进化:Leader Epoch
上面的 LEO/HW 机制在正常运行时很完美,但在 Broker 宕机重启 或 Leader 切换 的极端边缘场景下,可能会导致:
- 数据丢失。
- 数据不一致(Divergence):Leader 和 Follower 同一个 Offset 上的数据不一样。
原因:Follower 依赖 HW 进行截断,但 Follower 的 HW 更新有滞后性(如上图所示,慢一拍)。如果此时挂了,Follower 可能会错误地把本来已经有的数据截断掉。
解决方案:Leader Epoch (版本号机制)
从 Kafka 0.11 开始,引入了 Leader Epoch 机制。
- 每当 Leader 变化一次,Epoch 加 1。
- 副本截断不再单纯依赖 HW,而是通过对比
(Epoch, Offset)对。 - 这就像给数据加了“朝代纪年法”,清朝的数据不能用明朝的剑来斩,从而完美解决了 HW 机制的数据丢失隐患。
6. 总结
Kafka 的副本同步协议是保证数据一致性的基石。
- LEO 是进度的终点,HW 是安全的终点。
- Follower 主动拉取:通过 Fetch 请求携带
fetch_offset,既是求数据,也是向 Leader 汇报进度。 - HW 更新滞后性:Follower 的 HW 更新往往滞后于 Leader 一个 RPC 周期。
- 消费可见性:只有 HW 之前的数据,才会被消费者看到,这是 Kafka 保证
at-least-once和避免读到脏数据的关键。
理解了这个“推拉”细节,你再看 Kafka 的监控指标(如 UnderReplicatedPartitions)时,就会有上帝视角了。
浙公网安备 33010602011771号