一、读前需知

针对Kafka消息重复的场景与解决方法的思路均来自@老周聊架构聊聊 Kafka:Kafka 如何保证一致性,我仅以一个初学者的视角进行笔记和总结,非常感谢作者的分析。需要的同学请移步至老周的文章,我这边只作为自己学习和记录的备份

二、HW 和 LEO(图片源自@老周聊架构)这些都是副本专属的属性

要想 Kafka 保证一致性,我们必须先了解 HW(High Watermark)高水位和 LEO(Log End Offset)日志末端位移,看下面这张图你就清晰了:

image.png

高水位的作用:

  • 定义消息可见性,即用来标识分区下的哪些消息是可以被消费者消费的。
  • 帮助 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 值,请看下图:
image.png
从图中可以看出,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 被更新的时机(图片源自@老周聊架构

image.png

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。

image.png

当生产者给主题分区发送一条消息后,状态变更为:

image.png

Follower 再次尝试从 Leader 拉取消息。和之前不同的是,这次有消息可以拉取了,因此状态进一步变更为:

image.png

这时,Follower 副本也成功地更新 LEO 为 1。此时,Leader 和 Follower 副本的 LEO 都是 1,但各自的高水位依然是 0,还没有被更新。它们需要在下一轮的拉取中被更新,如下图所示:

image.png

在新一轮的拉取请求中,由于位移值是 0 的消息已经拉取成功,因此 Follower 副本这次请求拉取的是位移值 =1 的消息。Leader 副本接收到此请求后,更新远程副本 LEO 为 1,然后更新 Leader 高水位为 1。做完这些之后,它会将当前已更新过的高水位值 1 发送给 Follower 副本。Follower 副本接收到以后,也将自己的高水位值更新成 1。至此,一次完整的消息同步周期就结束了。事实上,Kafka 就是利用这样的机制,实现了 Leader 和 Follower 副本之间的同步。

五、Leader Epoch 机制

详见:@老周聊架构聊聊 Kafka:Kafka 如何保证一致性 能力有限确实没有看懂


爱跑步的猕猴桃
1 声望0 粉丝