一、读前需知
针对Kafka消息重复的场景与解决方法的思路均来自@老周聊架构聊聊 Kafka:Kafka 如何保证一致性,我仅以一个初学者的视角进行笔记和总结,非常感谢作者的分析。需要的同学请移步至老周的文章,我这边只作为自己学习和记录的备份
二、HW 和 LEO(图片源自@老周聊架构)这些都是副本专属的属性
要想 Kafka 保证一致性,我们必须先了解 HW(High Watermark)高水位和 LEO(Log End Offset)日志末端位移,看下面这张图你就清晰了:
高水位的作用:
- 定义消息可见性,即用来标识分区下的哪些消息是可以被消费者消费的。
- 帮助 Kafka 完成副本同步
这里我们不讨论 Kafka 事务,因为事务机制会影响消费者所能看到的消息的范围,它不只是简单依赖高水位来判断。它依靠一个名为 LSO(Log Stable Offset)的位移值来判断事务型消费者的可见性。
日志末端位移的作用:
- 副本写入下一条消息的位移值
- 数字 15 所在的方框是虚线,这就说明,这个副本当前只有 15 条消息,位移值是从 0 到 14,下一条新消息的位移是 15。
- 介于高水位和 LEO 之间的消息就属于未提交消息。这也反应出一个事实,那就是:同一个副本对象,其高水位值不会大于 LEO 值。
高水位和 LEO 是副本对象的两个重要属性。Kafka 所有副本都有对应的高水位和 LEO 值,而不仅仅是 Leader 副本。只不过 Leader 副本比较特殊,Kafka 使用 Leader 副本的高水位来定义所在分区的高水位。换句话说,分区的高水位就是其 Leader 副本的高水位。
三、HW 和 LEO 的更新机制
现在,我们知道了每个副本对象都保存了一组高水位值和 LEO 值,但实际上,在 Leader 副本所在的 Broker 上,还保存了其他 Follower 副本的 LEO 值,请看下图:
从图中可以看出,Broker 0 上保存了某分区的 Leader 副本和所有 Follower 副本的 LEO 值。而 Broker 1 上仅仅保存了该分区的某个 Follower 副本。Kafka 把 Broker 0 上保存的这些 Follower 副本又称为远程副本(Remote Replica)。Kafka 副本机制在运行过程中,会更新 Broker 1 上 Follower 副本的 HW 和 LEO 值,同时也会更新 Broker 0 上 Leader 副本的 HW 和 LEO 以及所有远程副本的 LEO,但它不会更新远程副本的 HW,也就是我在图中标记为灰色的部分。
- Leader 副本所在的 Broker 上只有重置更新远程副本的 LEO,并没有远程副本的
- HW 和 LEO 被更新的时机(图片源自@老周聊架构)
3.1 Leader副本
处理生产者请求的逻辑如下:
- 写入消息到本地磁盘
更新分区高水位值
- 获取 Leader 副本所在 Broker 端保存的所有远程副本 LEO 值 (LEO-1, LEO-2, LEO-3, ……,LEO-n)
- 获取 Leader 副本高水位值:currentHW
- 更新 currentHW = max{currentHW, min(LEO-1, LEO-2, ……,LEO-n)}
处理Follower 副本拉取消息的逻辑如下:
- 读取磁盘(或页缓存)中的消息数据
- 使用 Follower 副本发送请求中的位移值更新远程副本 LEO 值
- 更新分区高水位值(具体步骤与处理生产者请求的步骤相同)
3.2 Follower 副本
从 Leader 拉取消息的处理逻辑如下:
- 写入消息到本地磁盘
- 更新 LEO 值
更新高水位值
- 获取 Leader 发送的高水位值:currentHW
- 获取步骤 2 中更新过的 LEO 值:currentLEO
- 更新高水位为 min(currentHW, currentLEO)
四、副本同步机制(图片源自@老周聊架构)
搞清楚了上面 HW 和 LEO 的更新机制后,我们举一个单分区且有两个副本的主题来演示下 Kafka 副本同步的全流程。
当生产者发送一条消息时,Leader 和 Follower 副本对应的 HW 和 LEO 是怎么被更新的呢?
首先是初始状态。下面这张图中的 remote LEO 就是刚才的远程副本的 LEO 值。在初始状态时,所有值都是 0。
当生产者给主题分区发送一条消息后,状态变更为:
Follower 再次尝试从 Leader 拉取消息。和之前不同的是,这次有消息可以拉取了,因此状态进一步变更为:
这时,Follower 副本也成功地更新 LEO 为 1。此时,Leader 和 Follower 副本的 LEO 都是 1,但各自的高水位依然是 0,还没有被更新。它们需要在下一轮的拉取中被更新,如下图所示:
在新一轮的拉取请求中,由于位移值是 0 的消息已经拉取成功,因此 Follower 副本这次请求拉取的是位移值 =1 的消息。Leader 副本接收到此请求后,更新远程副本 LEO 为 1,然后更新 Leader 高水位为 1。做完这些之后,它会将当前已更新过的高水位值 1 发送给 Follower 副本。Follower 副本接收到以后,也将自己的高水位值更新成 1。至此,一次完整的消息同步周期就结束了。事实上,Kafka 就是利用这样的机制,实现了 Leader 和 Follower 副本之间的同步。
五、Leader Epoch 机制
详见:@老周聊架构聊聊 Kafka:Kafka 如何保证一致性 能力有限确实没有看懂
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。