除了读写副本、管理分区和副本的功能之外,副本管理器还有一个重要的功能,那就是管理 ISR。这里的管理主要体现在两个方法:
- maybeShrinkIsr 方法:作用是阶段性地查看 ISR 中的副本集合是否需要收缩;收缩是指,把 ISR 副本集合中那些与 Leader 差距过大的副本移除的过程。所谓的差距过大,就是 ISR 中 Follower 副本滞后 Leader 副本的时间,超过了 Broker 端参数 replica.lag.time.max.ms 值的 1.5 倍。
- maybePropagateIsrChanges 方法:作用是定期向集群 Broker 传播 ISR 的变更。
maybeShrinkIsr 方法
maybeShrinkIsr的逻辑就是遍历该副本管理器上所有分区对象,依次为这些分区中状态为 Online 的分区,执行 Partition 类的 maybeShrinkIsr 方法:
private def maybeShrinkIsr(): Unit = {
trace("Evaluating ISR list of partitions to see which replicas can be removed from the ISR")
// Shrink ISRs for non offline partitions
allPartitions.keys.foreach { topicPartition =>
nonOfflinePartition(topicPartition).foreach(_.maybeShrinkIsr(config.replicaLagTimeMaxMs))
}
}
它实际上执行了partition的maybeShrinkIsr方法
def maybeShrinkIsr(replicaMaxLagTimeMs: Long): Unit = {
val leaderHWIncremented = inWriteLock(leaderIsrUpdateLock) {
leaderLogIfLocal match {
// 如果是Leader副本
case Some(leaderLog) =>
// 获取不同步的副本Id列表
val outOfSyncReplicaIds = getOutOfSyncReplicas(replicaMaxLagTimeMs)
// 如果存在不同步的副本Id列表
if (outOfSyncReplicaIds.nonEmpty) {
// 计算收缩之后的ISR列表
val newInSyncReplicaIds = inSyncReplicaIds -- outOfSyncReplicaIds
assert(newInSyncReplicaIds.nonEmpty)
info("Shrinking ISR from %s to %s. Leader: (highWatermark: %d, endOffset: %d). Out of sync replicas: %s."
.format(inSyncReplicaIds.mkString(","),
newInSyncReplicaIds.mkString(","),
leaderLog.highWatermark,
leaderLog.logEndOffset,
outOfSyncReplicaIds.map { replicaId =>
s"(brokerId: $replicaId, endOffset: ${getReplicaOrException(replicaId).logEndOffset})"
}.mkString(" ")
)
)
// update ISR in zk and in cache
// 更新ZooKeeper中分区的ISR数据以及Broker的元数据缓存中的数据
shrinkIsr(newInSyncReplicaIds)
// we may need to increment high watermark since ISR could be down to 1
// 尝试更新Leader副本的高水位值
maybeIncrementLeaderHW(leaderLog)
} else {
false
}
// 如果不是Leader副本,什么都不做
case None => false // do nothing if no longer leader
}
}
// some delayed operations may be unblocked after HW changed
// 如果Leader副本的高水位值抬升了
if (leaderHWIncremented)
// 尝试解锁一下延迟请求
tryCompleteDelayedRequests()
}
另外下面再介绍一下partition的maybeExpandIsr方法。
maybeExpandIsr方法用于将指定的副本在满足条件下加入到 ISR 集合中
private def maybeExpandIsr(followerReplica: Replica, followerFetchTimeMs: Long): Unit = {
inWriteLock(leaderIsrUpdateLock) {
// check if this replica needs to be added to the ISR
leaderLogIfLocal.foreach { leaderLog => // 只有当本地副本是 leader 副本时,才执行扩张操作,因为 ISR 集合由 leader 副本维护
// 获取 leader 副本对应的 HW 值
val leaderHighwatermark = leaderLog.highWatermark
// 判断当前 follower 是否应该被加入到 ISR 集合,并在成功加入后更新相关信息
if (!inSyncReplicaIds.contains(followerReplica.brokerId) // follower 副本不在 ISR 集合中
&& isFollowerInSync(followerReplica, leaderHighwatermark) // follower 副本的 LEO 已经追赶上 leader 副本的 HW 值
) {
val newInSyncReplicaIds = inSyncReplicaIds + followerReplica.brokerId
info(s"Expanding ISR from ${inSyncReplicaIds.mkString(",")} " +
s"to ${newInSyncReplicaIds.mkString(",")}")
// update ISR in ZK and cache
expandIsr(newInSyncReplicaIds)
}
}
}
}
maybePropagateIsrChanges 方法
ISR 收缩之后,ReplicaManager 还需要将这个操作的结果传递给集群的其他 Broker,以同步这个操作的结果。这是由 ISR 通知事件来完成的。
// 周期性将 ISR 集合发生变化的 topic 副本信息更新到 ZK 相应节点下,
// Kafka 集群控制器基于 ZK 的 Watcher 机制监听相应节点,并在节点内容发生变化时向所有可用的 broker 节点发送 UpdateMetadataRequest 请求,
// 以更新相应 broker 节点本地管理的整个集群中所有分区的状态信息。
def maybePropagateIsrChanges(): Unit = {
val now = System.currentTimeMillis()
isrChangeSet synchronized {
// ISR变更传播的条件,需要同时满足:
// 1. 存在尚未被传播的ISR变更
// 2. 最近5秒没有任何ISR变更,或者自上次ISR变更已经有超过1分钟的时间
if (isrChangeSet.nonEmpty &&
(lastIsrChangeMs.get() + ReplicaManager.IsrChangePropagationBlackOut < now ||
lastIsrPropagationMs.get() + ReplicaManager.IsrChangePropagationInterval < now)) {
// 创建ZooKeeper相应的Znode节点
zkClient.propagateIsrChanges(isrChangeSet)
// 清空isrChangeSet集合
isrChangeSet.clear()
// 更新最近ISR变更时间戳
lastIsrPropagationMs.set(now)
}
}
}