KAFKA源码阅读——ReplicaFetcherManager,同步log

本文详细介绍了KAFKA中,当broker成为Follower时,如何通过ReplicaFetcherManager进行日志同步。具体包括根据leader分配Fetcher,启动FetcherThread执行Fetch操作,处理Fetch响应并在本地log中更新数据。还讨论了当follower的offset超出leader允许范围时,如何处理unclean leader selection情况下的日志截断问题。

ReplicaManager.makeFollowers函数中,对于成为Follower的broker,需要执行这么一段代码:

val partitionsToMakeFollowerWithLeaderAndOffset = partitionsToMakeFollower.map(partition =>
          new TopicAndPartition(partition) -> BrokerAndInitialOffset(
            leaders.find(_.id == partition.leaderReplicaIdOpt.get).get,
            partition.getReplica().get.logEndOffset.messageOffset)).toMap
replicaFetcherManager.addFetcherForPartitions(partitionsToMakeFollowerWithLeaderAndOffset)

这段代码首先获取Follower对应的(leader,leo),之后调用addFetcherForPartitions将这些partition纳入replicaFetcherManager的管理下,并不断同步leader中的log到follower中。
这里贴一下addFetcherForPartitions的代码:

 def addFetcherForPartitions(partitionAndOffsets: Map[TopicAndPartition, BrokerAndInitialOffset]) {
    mapLock synchronized {
      val partitionsPerFetcher = partitionAndOffsets.groupBy{ case(topicAndPartition, brokerAndInitialOffset) =>
        //fetchId是hash后取余,不同的topicAndPartition可能分配到同一个线程上
        //另外groupby还有一个broker参数,也就是不同的broker肯定不在一个组
        BrokerAndFetcherId(brokerAndInitialOffset.broker, getFetcherId(topicAndPartition.topic, topicAndPartition.partition))}
      for ((brokerAndFetcherId, partitionAndOffsets) <- partitionsPerFetcher) {
        var fetcherThread: AbstractFetcherThread = null
        fetcherThreadMap.get(brokerAndFetcherId) match {
          case Some(f) => fetcherThread = f
          case None =>
            //每个Thread对应一个broker上的多个partition,且broker为这些partition的leader
            fetcherThread = createFetcherThread(brokerAndFetcherId.fetcherId, brokerAndFetcherId.broker)
            fetcherThreadMap.put(brokerAndFetcherId, fetcherThread)
            fetcherThread.start
        }
        fetcherThreadMap(brokerAndFetcherId).addPartitions(partitionAndOffsets.map { case (topicAndPartition, brokerAndInitOffset) =>
          topicAndPartition -> brokerAndInitOffset.initOffset
        })
      }
    }
  }

这段代码做了如下的工作:
1. 根据leader broker 和 hash(topicAndPartition)%numFetchers将这些partitionAndOffsets进行groupby,结果就是同一个leader broker 下的几个topicAndPartition会分配到一组,因为对topicAndPartition进行了hash取余得到FetcherId,不同的topicAndPartition可能有相同的FetcherId。
2. 对于每个BrokerAndFetcherId 分配一个FetcherThread;
3. 调用FetcherThread.addPartitions给该线程添加Fetch任务;
添加任务后,FetcherThread会不停地同步执行Fetch操作:

response = simpleConsumer.fetch(fetchRequest)

simpleConsumer是连接到leader broker的,leader broker收到FetchRequest后会记录Fetch的起始offset,作为Follower的leo,同时会响应返回follower所需的数据。一旦收到response,Follower会解析data中包含的最后一条message的offset,作为下次Fetch的起点。

//从response中获取messages
val messages = partitionData.messages.asInstanceOf[ByteBufferMessageSet]
val validBytes = messages.validBytes
//下一次fetch的offset
val newOffset = messages.shallowIterator.toSeq.lastOption match {
    case Some(m: MessageAndOffset) => m.nextOffset
    case None => currentOffset.get
}

之后会调用processPartitionData函数,处理Fetch回来的数据:添加到本地log中,同时更新replica的hw。

replica.log.get.append(messageSet, assignOffsets = false)
//这里更新hw,当前的logEndOffset 与 leader返回的结果比较,求较小的
val followerHighWatermark=replica.logEndOffset.messageOffset.min(partitionData.hw)

除此以外,还需要考虑意外的情况,FetcherRequest请求中的offset可能不在leader允许的范围内,会返回ErrorMapping.OffsetOutOfRangeCode错误,这种情况下通过ReplicaFetcherThread.handleOffsetOutOfRange获取新的offset,作为下次Fetch的起点。这个函数里面对leader.leo < replica.leo的情况专门做了处理:

if (leaderEndOffset < replica.logEndOffset.messageOffset) {
      //在截断follower的log之前,需要在配置中查看unclean leader selection 是否允许
      //造成这种情况的原因是leader挂了以后,ISR为空,只有选择不在ISR中的Follower作为leader;
      //而当挂掉的leader复活后,从新的leader拉取数据时就会出现这种场景,follower的offset > leader 的offset
      if (!LogConfig.fromProps(brokerConfig.props.props, AdminUtils.fetchTopicConfig(replicaMgr.zkClient,
        topicAndPartition.topic)).uncleanLeaderElectionEnable) {
        //如果不允许unclean Leader Election,这种情况不应该发生,直接停止
        Runtime.getRuntime.halt(1)
      }
      //本地log截断至leader leo
      replicaMgr.logManager.truncateTo(Map(topicAndPartition -> leaderEndOffset))
      //返回leader leo
      leaderEndOffset

注释写的比较明了了,就是在允许unclean leader selection时,可能发生 follower的leo大于leader的leo的情况,需要截断follower的log到leader的leo,下次fetch时从leader leo开始。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值