这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
代码:kafka/core/src/main/scala/kafka/server/AbstractFetcherThread.scala
写这篇文章主要是想看看,副本拉取 msg 的过程会发生什么?针对几个问题:
- 拉取流程
- 日志截断怎么发生
- epoch 是怎么嵌入在流程里面?
AbstractFetcherThread
其他比如 ReplicaFetcherThread 副本管理类都是继承于此,而且里有作为 副本拉取线程 的核心方法:dowork() 。那就来看看这个方法具体设计:
override def doWork(): Unit = {
maybeTruncate() // 执行副本截断操作
maybeFetch() // 执行消息获取操作
}
首先 dowork 是循环操作,在这个循环操作中,只进行两件事:副本消息截断,然后拉取消息。
但是,为什么是先截断,再拉取消息?
其实这个含有另外一个问题,为什么要截断消息?
截断和拉取
因为分区 leader 在整个分布式系统中是会随时改变的。每当有新的 leader 选举出来,followers 首先要干的事情就是要和当前 leader 的消息序列保持一致,这个时候就需要截断自己的消息序列 (当然如果一样不需要截断)。
如果出现 leader→follower,那 leader 本身也需要将本地LEO → HW 处。
这个时候才能开始拉取 leader 的消息。
- 截断 ⇒ 如果出现 leader 选举,是为了和新 leader 保持一直的消息序列
- 拉取 ⇒ 正式开始同步 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)
}
}
- 首先,是对分区状态进行分组。
- 既然是做截断操作的,那么该方法操作的就只能是处于截断中状态的分区。
- 代码会判断这些分区是否存在对应的 Leader Epoch 值,并按照有无 Epoch 值进行分组
这就是 fetchTruncatingPartitions 方法做的事情。