kafka 副本同步机制「循环doWork」

422 阅读2分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

代码:kafka/core/src/main/scala/kafka/server/AbstractFetcherThread.scala

写这篇文章主要是想看看,副本拉取 msg 的过程会发生什么?针对几个问题:

  1. 拉取流程
  2. 日志截断怎么发生
  3. epoch 是怎么嵌入在流程里面?

AbstractFetcherThread

其他比如 ReplicaFetcherThread 副本管理类都是继承于此,而且里有作为 副本拉取线程 的核心方法:dowork() 。那就来看看这个方法具体设计:

override def doWork(): Unit = {
  maybeTruncate()   // 执行副本截断操作
  maybeFetch()      // 执行消息获取操作
}

首先 dowork 是循环操作,在这个循环操作中,只进行两件事:副本消息截断,然后拉取消息。

但是,为什么是先截断,再拉取消息?

其实这个含有另外一个问题,为什么要截断消息?

截断和拉取

因为分区 leader 在整个分布式系统中是会随时改变的。每当有新的 leader 选举出来,followers 首先要干的事情就是要和当前 leader 的消息序列保持一致,这个时候就需要截断自己的消息序列 (当然如果一样不需要截断)。

如果出现 leader→follower,那 leader 本身也需要将本地LEO → HW 处。

这个时候才能开始拉取 leader 的消息。

  1. 截断 ⇒ 如果出现 leader 选举,是为了和新 leader 保持一直的消息序列
  2. 拉取 ⇒ 正式开始同步 leader 消息

maybeTruncate

private def maybeTruncate(): Unit = {
  // 依据有无Leader Epoch值进行分组
  val (partitionsWithEpochs, partitionsWithoutEpochs) = fetchTruncatingPartitions()
  // 对于有Leader Epoch值的分区,将日志截断到Leader Epoch值对应的位移值处
  if (partitionsWithEpochs.nonEmpty) {
    truncateToEpochEndOffsets(partitionsWithEpochs)
  }
  // 对于没有Leader Epoch值的分区,将日志截断到高水位值处
  if (partitionsWithoutEpochs.nonEmpty) {
    truncateToHighWatermark(partitionsWithoutEpochs)
  }
}
  1. 首先,是对分区状态进行分组。
  2. 既然是做截断操作的,那么该方法操作的就只能是处于截断中状态的分区。
  3. 代码会判断这些分区是否存在对应的 Leader Epoch 值,并按照有无 Epoch 值进行分组

这就是 fetchTruncatingPartitions 方法做的事情。