privatedefisBrokerEpochStale(brokerEpochInRequest: Long): Boolean = { // Broker epoch in LeaderAndIsr/UpdateMetadata/StopReplica request is unknown // if the controller hasn't been upgraded to use KIP-380 if (brokerEpochInRequest == AbstractControlRequest.UNKNOWN_BROKER_EPOCH) false else { // brokerEpochInRequest > controller.brokerEpoch is possible in rare scenarios where the controller gets notified // about the new broker epoch and sends a control request with this epoch before the broker learns about it // // 首先,每个 broker 上都有一个 controller 实例, // 但是只有选举成功的那个 broker 上的 controller 实例才会履行职责,管理整个集群。 // // 我理解,这里的 controller.brokerEpoch 是指真正的 controller 分配给当前 broker 的 epoch, // // 所以,存在下面的情况: // 真正的 controller 中某个线程,为当前 broker 确定了更大的 brokerEpoch, // 但是还没来得及通知当前 broker(或者说当前 broker 还没有把数据更新到本地的 controller.brokerEpoch 中), // 此时,leaderAndIsr 请求中的 brokerEpochInRequest 就会大于controller.brokerEpoch // // 这种情况也认为是合法的请求。 brokerEpochInRequest < controller.brokerEpoch } }
defbecomeLeaderOrFollower(correlationId: Int, leaderAndIsrRequest: LeaderAndIsrRequest, onLeadershipChange: (Iterable[Partition], Iterable[Partition]) => Unit): LeaderAndIsrResponse = { val startMs = time.milliseconds() replicaStateChangeLock synchronized { val controllerId = leaderAndIsrRequest.controllerId val requestPartitionStates = leaderAndIsrRequest.partitionStates.asScala ······
val response = {
if (leaderAndIsrRequest.controllerEpoch < controllerEpoch) { // 忽略来自旧 controller 的请求 stateChangeLogger.warn(s"Ignoring LeaderAndIsr request from controller $controllerId with " + s"correlation id $correlationId since its controller epoch ${leaderAndIsrRequest.controllerEpoch} is old. " + s"Latest known controller epoch is $controllerEpoch") leaderAndIsrRequest.getErrorResponse(0, Errors.STALE_CONTROLLER_EPOCH.exception)
} else {
val responseMap = new mutable.HashMap[TopicPartition, Errors] controllerEpoch = leaderAndIsrRequest.controllerEpoch
val partitionStates = new mutable.HashMap[Partition, LeaderAndIsrPartitionState]()
// 从本地缓存中找出要处理的 partition,如果没找到则新建一个 val topicPartition = newTopicPartition(partitionState.topicName, partitionState.partitionIndex) val partitionOpt = getPartition(topicPartition) match {
caseHostedPartition.Offline => // 分区离线,返回 None stateChangeLogger.warn(s"Ignoring LeaderAndIsr request from " + s"controller $controllerId with correlation id $correlationId " + s"epoch $controllerEpoch for partition $topicPartition as the local replica for the " + "partition is in an offline log directory") responseMap.put(topicPartition, Errors.KAFKA_STORAGE_ERROR) None
// 本地缓存中的 leaderEpoch val currentLeaderEpoch = partition.getLeaderEpoch // 请求中的 leaderEpoch val requestLeaderEpoch = partitionState.leaderEpoch
if (requestLeaderEpoch > currentLeaderEpoch) { // If the leader epoch is valid record the epoch of the controller that made the leadership decision. // This is useful while updating the isr to maintain the decision maker controller's epoch in the zookeeper path // 记录到合法的 partitionStates 中,以便后续处理(makeLeader、makeFollower) if (partitionState.replicas.contains(localBrokerId)) partitionStates.put(partition, partitionState) else { stateChangeLogger.warn(s"Ignoring LeaderAndIsr request from controller $controllerId with " + s"correlation id $correlationId epoch $controllerEpoch for partition $topicPartition as itself is not " + s"in assigned replica list ${partitionState.replicas.asScala.mkString(",")}") responseMap.put(topicPartition, Errors.UNKNOWN_TOPIC_OR_PARTITION) } } elseif (requestLeaderEpoch < currentLeaderEpoch) { stateChangeLogger.warn(s"Ignoring LeaderAndIsr request from " + s"controller $controllerId with correlation id $correlationId " + s"epoch $controllerEpoch for partition $topicPartition since its associated " + s"leader epoch $requestLeaderEpoch is smaller than the current " + s"leader epoch $currentLeaderEpoch") responseMap.put(topicPartition, Errors.STALE_CONTROLLER_EPOCH) } else { stateChangeLogger.info(s"Ignoring LeaderAndIsr request from " + s"controller $controllerId with correlation id $correlationId " + s"epoch $controllerEpoch for partition $topicPartition since its associated " + s"leader epoch $requestLeaderEpoch matches the current leader epoch") responseMap.put(topicPartition, Errors.STALE_CONTROLLER_EPOCH) } } }
// 根据请求中的 leader 信息,将 partition 分为两组单独处理 val partitionsToBeLeader = partitionStates.filter { case (_, partitionState) => partitionState.leader == localBrokerId } val partitionsToBeFollower = partitionStates.filter { case (k, _) => !partitionsToBeLeader.contains(k) }
val highWatermarkCheckpoints = newLazyOffsetCheckpoints(this.highWatermarkCheckpoints) val partitionsBecomeLeader = if (partitionsToBeLeader.nonEmpty) {
// 变为 Leader makeLeaders(controllerId, controllerEpoch, partitionsToBeLeader, correlationId, responseMap, highWatermarkCheckpoints) } else Set.empty[Partition] val partitionsBecomeFollower = if (partitionsToBeFollower.nonEmpty) {
privatedefmakeFollowers(controllerId: Int, controllerEpoch: Int, partitionStates: Map[Partition, LeaderAndIsrPartitionState], correlationId: Int, responseMap: mutable.Map[TopicPartition, Errors], highWatermarkCheckpoints: OffsetCheckpoints) : Set[Partition] = { val traceLoggingEnabled = stateChangeLogger.isTraceEnabled partitionStates.forKeyValue { (partition, partitionState) => if (traceLoggingEnabled) stateChangeLogger.trace(s"Handling LeaderAndIsr request correlationId $correlationId from controller $controllerId " + s"epoch $controllerEpoch starting the become-follower transition for partition ${partition.topicPartition} with leader " + s"${partitionState.leader}") responseMap.put(partition.topicPartition, Errors.NONE) }
val partitionsToMakeFollower: mutable.Set[Partition] = mutable.Set() try { // TODO: Delete leaders from LeaderAndIsrRequest partitionStates.forKeyValue { (partition, partitionState) => val newLeaderBrokerId = partitionState.leader try { // 从 controller 发过来的 metadata 缓存中拿到所有存活的 broker // 根据 brokerId 匹配到新的 leader metadataCache.getAliveBrokers.find(_.id == newLeaderBrokerId) match { // Only change partition state when the leader is available caseSome(_) => // 更新 leaderBrokerId、清空 ISR、查询zk上的配置并创建日志目录 if (partition.makeFollower(partitionState, highWatermarkCheckpoints)) partitionsToMakeFollower += partition else stateChangeLogger.info(s"Skipped the become-follower state change after marking its partition as " + s"follower with correlation id $correlationId from controller $controllerId epoch $controllerEpoch " + s"for partition ${partition.topicPartition} (last update " + s"controller epoch ${partitionState.controllerEpoch}) " + s"since the new leader $newLeaderBrokerId is the same as the old leader") caseNone => // Controller 发送的 leaderAndIsr 告知新 leader 是 newLeaderBrokerId // Controller 发送的 metadataCache 中找不到 newLeaderBrokerId // 处理不了啦 // // The leader broker should always be present in the metadata cache. // If not, we should record the error message and abort the transition process for this partition stateChangeLogger.error(s"Received LeaderAndIsrRequest with correlation id $correlationId from " + s"controller $controllerId epoch $controllerEpoch for partition ${partition.topicPartition} " + s"(last update controller epoch ${partitionState.controllerEpoch}) " + s"but cannot become follower since the new leader $newLeaderBrokerId is unavailable.") // Create the local replica even if the leader is unavailable. This is required to ensure that we include // the partition's high watermark in the checkpoint file (see KAFKA-1647) partition.createLogIfNotExists(isNew = partitionState.isNew, isFutureReplica = false, highWatermarkCheckpoints) } } catch { case e: KafkaStorageException => ······ responseMap.put(partition.topicPartition, Errors.KAFKA_STORAGE_ERROR) } }
// 移除失效的 fetcherThread。 // 比如说原先是通过 fetcherA 向 brokerA 拉取数据,后来 brokerB 上的 replica 选举成功成为 leader了, // 就要向 brokerB 拉取数据,此时需要删掉 fetcherA replicaFetcherManager.removeFetcherForPartitions(partitionsToMakeFollower.map(_.topicPartition)) stateChangeLogger.info(s"Stopped fetchers as part of become-follower request from controller $controllerId " + s"epoch $controllerEpoch with correlation id $correlationId for ${partitionsToMakeFollower.size} partitions")
if (isShuttingDown.get()) { if (traceLoggingEnabled) { partitionsToMakeFollower.foreach { partition => stateChangeLogger.trace(s"Skipped the adding-fetcher step of the become-follower state " + s"change with correlation id $correlationId from controller $controllerId epoch $controllerEpoch for " + s"partition ${partition.topicPartition} with leader ${partitionStates(partition).leader} " + "since it is shutting down") } } } else { // we do not need to check if the leader exists again since this has been done at the beginning of this process // 查找 leader 的基本信息,以便同步数据 val partitionsToMakeFollowerWithLeaderAndOffset = partitionsToMakeFollower.map { partition => val leader = metadataCache.getAliveBrokers.find(_.id == partition.leaderReplicaIdOpt.get).get .brokerEndPoint(config.interBrokerListenerName) val fetchOffset = partition.localLogOrException.highWatermark partition.topicPartition -> InitialFetchState(leader, partition.getLeaderEpoch, fetchOffset) }.toMap
// 创建 log fetcher,同步数据 replicaFetcherManager.addFetcherForPartitions(partitionsToMakeFollowerWithLeaderAndOffset) } } catch { case e: Throwable => stateChangeLogger.error(s"Error while processing LeaderAndIsr request with correlationId $correlationId " + s"received from controller $controllerId epoch $controllerEpoch", e) // Re-throw the exception for it to be caught in KafkaApis throw e }
if (traceLoggingEnabled) partitionStates.keys.foreach { partition => stateChangeLogger.trace(s"Completed LeaderAndIsr request correlationId $correlationId from controller $controllerId " + s"epoch $controllerEpoch for the become-follower transition for partition ${partition.topicPartition} with leader " + s"${partitionStates(partition).leader}") }
initialFetchStates.forKeyValue { (tp, initialFetchState) => // We can skip the truncation step iff the leader epoch matches the existing epoch val currentState = partitionStates.stateValue(tp)
/** * Handle a partition whose offset is out of range and return a new fetch offset. * * 处理拉取数据时,fetch offset 超出 leader 所保存的日志范围之外的情况。 * * 查询 leader 的 log end offset,结合本地的 log end offset, * 找到一个合理的 truncate offset * */ protecteddeffetchOffsetAndTruncate(topicPartition: TopicPartition, currentLeaderEpoch: Int): PartitionFetchState = {
// 查询本地 ReplicaManager 中缓存的 topicPartition 的 log end offset val replicaEndOffset = logEndOffset(topicPartition)
/** * Unclean leader election: A follower goes down, in the meanwhile the leader keeps appending messages. The follower comes back up * and before it has completely caught up with the leader's logs, all replicas in the ISR go down. The follower is now uncleanly * elected as the new leader, and it starts appending messages from the client. The old leader comes back up, becomes a follower * and it may discover that the current leader's end offset is behind its own end offset. * * Unclean leader election: * 某个 follower 挂了,同时,leader 一直在写入消息。 * 然后 follower 又恢复了,但还没有追上 leader 的 log,此时如果 isr 中的副本都挂了(包括 leader) * 那么这个 follower 会通过 unclean elect 成为 leader,然后开始接受 client 的数据。 * 此时,如果原 leader 又恢复了,成为了一个 follower,他就会发现新 leader 的 log end offset 比自己的小 * * In such a case, truncate the current follower's log to the current leader's end offset and continue fetching. * * 这种情况下,将截断当前 follower(旧 leader) 的 log 到新 leader 的 log end offset,然后继续 fetching。 * * There is a potential for a mismatch between the logs of the two replicas here. We don't fix this mismatch as of now. * * 这情况暂时不修 * * 注意:unclean election 可以通过配置文件控制,默认是关闭的 */
// 向 leader 发送请求(ApiKeys.LIST_OFFSETS),查询 log end offset val leaderEndOffset = fetchLatestOffsetFromLeader(topicPartition, currentLeaderEpoch) if (leaderEndOffset < replicaEndOffset) { warn(s"Reset fetch offset for partition $topicPartition from $replicaEndOffset to current " + s"leader's latest offset $leaderEndOffset")
// 截取完毕,构造一个 fetching 状态的 state,以备拉取数据 PartitionFetchState(leaderEndOffset, Some(0), currentLeaderEpoch, state = Fetching) } else { /** * If the leader's log end offset is greater than the follower's log end offset, there are two possibilities: * 1. The follower could have been down for a long time and when it starts up, its end offset could be smaller than the leader's * start offset because the leader has deleted old logs (log.logEndOffset < leaderStartOffset). * 2. When unclean leader election occurs, it is possible that the old leader's high watermark is greater than * the new leader's log end offset. So when the old leader truncates its offset to its high watermark and starts * to fetch from the new leader, an OffsetOutOfRangeException will be thrown. After that some more messages are * produced to the new leader. While the old leader is trying to handle the OffsetOutOfRangeException and query * the log end offset of the new leader, the new leader's log end offset becomes higher than the follower's log end offset. * * 如果 leader log end offset >= follower log end offset,有 2 种情况: * 1. follower 挂了。当挂了很长时间,然后恢复的时候,还会出现 follower 的 log end offset 会比 leader 的 log start offset * 还小的情况。 * 2. unclean elect 时,旧 leader 的 hw > 新 leader 的 log end offset,此时旧 leader 会从 hw 截断, * 当旧 leader 再次从 leader 拉取数据时,会报 OffsetOutOfRangeException。此时新 leader 则继续接受数据, * 而旧 leader 会尝试处理这个异常,处理异常的过程中,会查询 leader 的 log end offset,最终就会触发这个场景。 * * In the first case, the follower's current log end offset is smaller than the leader's log start offset. So the * follower should truncate all its logs, roll out a new segment and start to fetch from the current leader's log * start offset. * * 第一种情况,follower 的 log end offset < leader 的 log start offset,follower 应该截掉所有的内容, * 创建一个新的 segment,然后从 leader 的 log start offset 最开头拉取数据。 * * In the second case, the follower should just keep the current log segments and retry the fetch. In the second * case, there will be some inconsistency of data between old and new leader. We are not solving it here. * If users want to have strong consistency guarantees, appropriate configurations needs to be set for both * brokers and producers. * * 第二种情况,follower 只需要继续 fetch 就可以了。但第二种情况下,可能会有数据不一致的问题,暂时不处理。 * 如果用户需要强一致性保证,需要同时在 broker 和 producer 进行设置。 * * Putting the two cases together, the follower should fetch from the higher one of its replica log end offset * and the current leader's log start offset. * * 综上所述,follower 需要从 local log end offset 和 leader log start offset 中选择一个较大的值进行 fetch。 */ val leaderStartOffset = fetchEarliestOffsetFromLeader(topicPartition, currentLeaderEpoch) warn(s"Reset fetch offset for partition $topicPartition from $replicaEndOffset to current " + s"leader's start offset $leaderStartOffset")
val offsetToFetch = Math.max(leaderStartOffset, replicaEndOffset)
// Only truncate log when current leader's log start offset is greater than follower's log end offset. // follower 的数据太旧了,全部删掉,创建一个从 leaderStartOffset 开始的新 segment if (leaderStartOffset > replicaEndOffset) truncateFullyAndStartAt(topicPartition, leaderStartOffset)
val initialLag = leaderEndOffset - offsetToFetch fetcherLagStats.getAndMaybePut(topicPartition).lag = initialLag PartitionFetchState(offsetToFetch, Some(initialLag), currentLeaderEpoch, state = Fetching) } }
/** * case class to keep partition offset and its state(truncatingLog, delayed) * This represents a partition as being either: * (1) Truncating its log, for example having recently become a follower * (2) Delayed, for example due to an error, where we subsequently back off a bit * (3) ReadyForFetch, the is the active state where the thread is actively fetching data. * * 保存 partition offset 和 state(truncating 或者 delayed),共有三种状态: * 1. Truncating 表示副本正在执行截断日志操作;比如最近刚成为 follower 的时候 * 2. Delayed 延迟状态;比如由于某种异常,导致了日志拉取延迟,落后于 leader * 3. ReadyForFetch,表示可以拉取数据 */ caseclassPartitionFetchState(fetchOffset: Long, lag: Option[Long], currentLeaderEpoch: Int, delay: Option[DelayedItem], state: ReplicaState) {
if (fetchRequestOpt.isEmpty) { trace(s"There are no active partitions. Back off for $fetchBackOffMs ms before sending a fetch request") partitionMapCond.await(fetchBackOffMs, TimeUnit.MILLISECONDS) }
/** * - Build a leader epoch fetch based on partitions that are in the Truncating phase * - Send OffsetsForLeaderEpochRequest, retrieving the latest offset for each partition's * leader epoch. This is the offset the follower should truncate to ensure * accurate log replication. * - Finally truncate the logs for partitions in the truncating phase and mark the * truncation complete. Do this within a lock to ensure no leadership changes can * occur during truncation. * * - 针对 Truncating 状态的 partition,构建一个 api 请求用于查询 leader epoch * - 发送 OffsetsForLeaderEpochRequest,从 leader 取回最新的 leaderEpoch 及对应的 log offset * 这个 log offset 就是应该截取的位置 * - 最后,执行 truncate 操作,并标记为 truncation complete 状态。这部分操作需在 partitionMapLock 中执行, * 以确保在此期间没有 leadership 变更。 * */ privatedeftruncateToEpochEndOffsets(latestEpochsForPartitions: Map[TopicPartition, EpochData]): Unit = {
//Ensure we hold a lock during truncation. inLock(partitionMapLock) { //Check no leadership and no leader epoch changes happened whilst we were unlocked, fetching epochs // // 外层从 partitionStates 中取的 topicPartition 来获取 latestEpoch, // 没有加锁,到这里有可能已经不存在了/被更新了,所以需要再做一次过滤,确保 // 1. 存在 // 2. leaderEpoch 相同(领导权未发生过变更) val epochEndOffsets = endOffsets.filter { case (tp, _) => val curPartitionState = partitionStates.stateValue(tp) val partitionEpochRequest = latestEpochsForPartitions.getOrElse(tp, { thrownewIllegalStateException( s"Leader replied with partition $tp not requested in OffsetsForLeaderEpoch request") }) val leaderEpochInRequest = partitionEpochRequest.currentLeaderEpoch.get curPartitionState != null && leaderEpochInRequest == curPartitionState.currentLeaderEpoch }
privatedefmaybeTruncateToEpochEndOffsets(fetchedEpochs: Map[TopicPartition, EpochEndOffset], latestEpochsForPartitions: Map[TopicPartition, EpochData]): ResultWithPartitions[Map[TopicPartition, OffsetTruncationState]] = { val fetchOffsets = mutable.HashMap.empty[TopicPartition, OffsetTruncationState] val partitionsWithError = mutable.HashSet.empty[TopicPartition]
fetchedEpochs.forKeyValue { (tp, leaderEpochOffset) => leaderEpochOffset.error match { caseErrors.NONE =>
// Returns truncation offset and whether this is the final offset to truncate to // // 处理每个 topic-partition 的日志截断。 // 返回要截断的 offset 以及是否为 最终 offset。 // // For each topic partition, the offset to truncate to is calculated based on leader's returned // epoch and offset: // // 对于每个 topic-partition,截断 offset 的计算依靠 leader 返回的 epoch 和 offset: // // -- If the leader replied with undefined epoch offset, we must use the high watermark. This can // happen if 1) the leader is still using message format older than KAFKA_0_11_0; 2) the follower // requested leader epoch < the first leader epoch known to the leader. // // -- 如果 leader 返回的 offset 是未定义(UNDEFINED_EPOCH_OFFSET),则必须使用 hw // 这种情况可能出现在: // 1)leader 仍在使用旧版本消息格式(早于 KAFKA_0_11_0); // 2)follower 请求的 leader epoch 小于 leader 所知道的最早 epoch // // -- If the leader replied with the valid offset but undefined leader epoch, we truncate to // leader's offset if it is lower than follower's Log End Offset. This may happen if the // leader is on the inter-broker protocol version < KAFKA_2_0_IV0 // // -- 如果 leader 返回了有效 offset 但未定义的 leaderEpoch(UNDEFINED_EPOCH), // 如果该 offset 小于 follower 的日志末尾(Log End Offset),就截断到 leader 的 offset。 // 这种情况可能发生在 leader 使用低于 KAFKA_2_0_IV0 的协议版本 // // -- If the leader replied with leader epoch not known to the follower, we truncate to the // end offset of the largest epoch that is smaller than the epoch the leader replied with, and // send OffsetsForLeaderEpochRequest with that leader epoch. In a more rare case, where the // follower was not tracking epochs smaller than the epoch the leader replied with, we // truncate the leader's offset (and do not send any more leader epoch requests). // // -- 如果 leader 返回的 epoch 对 follower 是未知的, // 就截断到比该 epoch 小的最大已知 epoch 的日志末尾 offset,并重新发起 OffsetsForLeaderEpoch 请求。 // 如果 follower 甚至没有记录这些较小 epoch 的信息(很少见), // 就直接截断到 leader 返回的 offset,并不再发起后续 epoch 请求。 // // -- Otherwise, truncate to min(leader's offset, end offset on the follower for epoch that // leader replied with, follower's Log End Offset). // // -- 否则,就截断到以下三者中的最小值: // leader 返回的 offset、follower 在该 epoch 上的末尾 offset、follower 的日志末尾 offset。 // val offsetTruncationState = getOffsetTruncationState(tp, leaderEpochOffset)
private[server] deftruncateToHighWatermark(partitions: Set[TopicPartition]): Unit = inLock(partitionMapLock) { val fetchOffsets = mutable.HashMap.empty[TopicPartition, OffsetTruncationState]
// 外层默认赋值是 hw,所以这里是从 hw 开始截断 for (tp <- partitions) { val partitionState = partitionStates.stateValue(tp) if (partitionState != null) { val highWatermark = partitionState.fetchOffset val truncationState = OffsetTruncationState(highWatermark, truncationCompleted = true)
info(s"Truncating partition $tp to local high watermark $highWatermark") // 执行日志截断 if (doTruncate(tp, truncationState)) fetchOffsets.put(tp, truncationState) } }
deftruncateTo(partitionOffsets: Map[TopicPartition, Long], isFuture: Boolean): Unit = { val affectedLogs = ArrayBuffer.empty[Log] for ((topicPartition, truncateOffset) <- partitionOffsets) { val log = { if (isFuture) futureLogs.get(topicPartition) else currentLogs.get(topicPartition) } // If the log does not exist, skip it if (log != null) { // May need to abort and pause the cleaning of the log, and resume after truncation is done. // Kafka 的 Log Cleaner 线程,用于清理压缩日志。 // // 如果截断 offset 小于当前活跃 segment 的起始 offset, // 说明 Cleaner 有可能在处理将被截断的 segment, // 需要通知 Log Cleaner 先暂停处理这个 log,避免出现一致性问题。 val needToStopCleaner = truncateOffset < log.activeSegment.baseOffset if (needToStopCleaner && !isFuture) abortAndPauseCleaning(topicPartition)
try { if (log.truncateTo(truncateOffset)) affectedLogs += log
// 如果截断位置在 active segment 前,意味着之前的 checkpoint 也可能过时,必须一并更新。 if (needToStopCleaner && !isFuture) maybeTruncateCleanerCheckpointToActiveSegmentBaseOffset(log, topicPartition)
private[kafka] deftruncateTo(targetOffset: Long): Boolean = { maybeHandleIOException(s"Error while truncating log to offset $targetOffset for $topicPartition in dir ${dir.getParent}") { if (targetOffset < 0) thrownewIllegalArgumentException(s"Cannot truncate partition $topicPartition to a negative offset (%d).".format(targetOffset)) if (targetOffset >= logEndOffset) { info(s"Truncating to $targetOffset has no effect as the largest offset in the log is ${logEndOffset - 1}")
// Always truncate epoch cache since we may have a conflicting epoch entry at the // end of the log from the leader. This could happen if this broker was a leader // and inserted the first start offset entry, but then failed to append any entries // before another leader was elected. lock synchronized { leaderEpochCache.foreach(_.truncateFromEnd(logEndOffset)) }
// 为 state 中增加一个 delay 值(来自配置 replica.fetch.backoff.ms 默认值 100), // 表示延迟 delay ms 后再重新尝试 handlePartitionsWithErrors(partitionsWithError, "maybeFetch")
if (fetchRequestOpt.isEmpty) { // 没有任何 fetch 任务时,等待一段时间(这个时间和上面的 delay 值取自同一个参数) trace(s"There are no active partitions. Back off for $fetchBackOffMs ms before sending a fetch request") partitionMapCond.await(fetchBackOffMs, TimeUnit.MILLISECONDS) }
inLock(partitionMapLock) { responseData.forKeyValue { (topicPartition, partitionData) => Option(partitionStates.stateValue(topicPartition)).foreach { currentFetchState => // It's possible that a partition is removed and re-added or truncated when there is a pending fetch request. // In this case, we only want to process the fetch response if the partition state is ready for fetch and // the current offset is the same as the offset requested. // // 只处理 partition fetch state == Fetching 且 offset 与预期一致的 TopicPartition
// 找到 request param val fetchPartitionData = sessionPartitions.get(topicPartition)
// 1. 是本次请求的 partition // 2. 请求中的 offset 和 partition state 中的 fetch offset 一致(即,在请求期间,partition state 没有更新过) // 3. state == Fetching && delayTime == 0 if (fetchPartitionData != null && fetchPartitionData.fetchOffset == currentFetchState.fetchOffset && currentFetchState.isReadyForFetch) {
// request leader epoch val requestEpoch = if (fetchPartitionData.currentLeaderEpoch.isPresent) Some(fetchPartitionData.currentLeaderEpoch.get().toInt) elseNone
partitionData.error match { caseErrors.NONE => try { // Once we hand off the partition data to the subclass, we can't mess with it any more in this thread // 一旦把数据传递给 process 流程,本线程就不能再修改这些数据了
// 更新 follower 的落后值 lag val validBytes = logAppendInfo.validBytes val nextOffset = if (validBytes > 0) logAppendInfo.lastOffset + 1else currentFetchState.fetchOffset val lag = Math.max(0L, partitionData.highWatermark - nextOffset) fetcherLagStats.getAndMaybePut(topicPartition).lag = lag ...... } } catch { case ime@( _: CorruptRecordException | _: InvalidRecordException) => // 消息 CRC 校验失败 或 消息格式非法 // we log the error and continue. This ensures two things // 1. If there is a corrupt message in a topic partition, it does not bring the fetcher thread // down and cause other topic partition to also lag // 2. If the message is corrupt due to a transient state in the log (truncation, partial writes // can cause this), we simply continue and should get fixed in the subsequent fetches error(s"Found invalid messages during fetch for partition $topicPartition " + s"offset ${currentFetchState.fetchOffset}", ime) partitionsWithError += topicPartition case e: KafkaStorageException => // 副本离线、IO 异常等情况 error(s"Error while processing data for partition $topicPartition " + s"at offset ${currentFetchState.fetchOffset}", e) markPartitionFailed(topicPartition) case t: Throwable => // stop monitoring this partition and add it to the set of failed partitions error(s"Unexpected error occurred while processing data for partition $topicPartition " + s"at offset ${currentFetchState.fetchOffset}", t) markPartitionFailed(topicPartition) } caseErrors.OFFSET_OUT_OF_RANGE => // follower 请求的 offset 不再 leader 的日志范围内 if (handleOutOfRangeError(topicPartition, currentFetchState, requestEpoch)) partitionsWithError += topicPartition
caseErrors.UNKNOWN_LEADER_EPOCH => // follower 请求中携带的 leader epoch > leader epoch debug(s"Remote broker has a smaller leader epoch for partition $topicPartition than " + s"this replica's current leader epoch of ${currentFetchState.currentLeaderEpoch}.") partitionsWithError += topicPartition
caseErrors.NOT_LEADER_OR_FOLLOWER => debug(s"Remote broker is not the leader for partition $topicPartition, which could indicate " + "that the partition is being moved") partitionsWithError += topicPartition
caseErrors.UNKNOWN_TOPIC_OR_PARTITION => warn(s"Received ${Errors.UNKNOWN_TOPIC_OR_PARTITION} from the leader for partition $topicPartition. " + "This error may be returned transiently when the partition is being created or deleted, but it is not " + "expected to persist.") partitionsWithError += topicPartition
case _ => error(s"Error for partition $topicPartition at offset ${currentFetchState.fetchOffset}", partitionData.error.exception) partitionsWithError += topicPartition } } } } } }
// 处理失败的分区(为 state 中增加一个 delay值,延迟一段时间后重试) if (partitionsWithError.nonEmpty) { handlePartitionsWithErrors(partitionsWithError, "processFetchRequest") } }
overridedefprocessPartitionData(topicPartition: TopicPartition, fetchOffset: Long, partitionData: FetchData): Option[LogAppendInfo] = { val logTrace = isTraceEnabled val partition = replicaMgr.nonOfflinePartition(topicPartition).get val log = partition.localLogOrException val records = toMemoryRecords(partitionData.records)
// 确保已经 truncate 到正确的位置 if (fetchOffset != log.logEndOffset) thrownewIllegalStateException("Offset mismatch for partition %s: fetched offset = %d, log end offset = %d.".format( topicPartition, fetchOffset, log.logEndOffset))
if (logTrace) trace("Follower has replica log end offset %d for partition %s. Received %d messages and leader hw %d" .format(log.logEndOffset, topicPartition, records.sizeInBytes, partitionData.highWatermark))
if (logTrace) trace("Follower has replica log end offset %d after appending %d bytes of messages for partition %s" .format(log.logEndOffset, records.sizeInBytes, topicPartition)) val leaderLogStartOffset = partitionData.logStartOffset
// leader 会在 follower 拉取数据的同时发送 log start offset 值,以便 follower 更新本地 log start offset log.maybeIncrementLogStartOffset(leaderLogStartOffset, LeaderOffsetIncremented) if (logTrace) trace(s"Follower set replica high watermark for partition $topicPartition to $followerHighWatermark")
// For the follower replica, we do not need to keep its segment base offset and physical position. // These values will be computed upon becoming leader or handling a preferred read replica fetch. // // 对于 follower replica,我们不需要保留其 segment base offset 和物理位置。 // 这些值将在成为 leader 时或处理 preferred read replica 请求时计算。
......
// 更新 metric if (partition.isReassigning && partition.isAddingLocalReplica) brokerTopicStats.updateReassignmentBytesIn(records.sizeInBytes) brokerTopicStats.updateReplicationBytesIn(records.sizeInBytes)