KafkaApis处理FetchRequest请求源码解析

KafkaApis模块是Kafka中负责不同业务请求的具体实现逻辑,本文主要讲一下KafkaApis处理FetchRequest请求的流程。

当状态为Follower的Replica向状态为Leader的Replica同步数据或者消费者获取数据时,Replica会发送FetchRequest给Leader所在的Broker Server,Broker Server在接收到FetchRequest请求时,会返回相应的数据,同时还会根据情况更新对应的元数据。其详细的过程如下:

def handleFetchRequest(request: RequestChannel.Request) {
    val fetchRequest = request.requestObj.asInstanceOf[FetchRequest]
    //根据fetchRequest获取指定的数据
    val dataRead = replicaManager.readMessageSets(fetchRequest)

    //如果更新请求是来自follower,还需要更新元数据详细以及响应那些延迟的DelayedFetch和DelayedProduce
    if(fetchRequest.isFromFollower)
      recordFollowerLogEndOffsets(fetchRequest.replicaId, dataRead.mapValues(_.offset))

    // 统计获取的数据量
    val bytesReadable = dataRead.values.map(_.data.messages.sizeInBytes).sum
    // 统计异常
    val errorReadingData = dataRead.values.foldLeft(false)((errorIncurred, dataAndOffset) =>
      errorIncurred || (dataAndOffset.data.error != ErrorMapping.NoError))
    /* 
       如果发生以下几种情况,立即响应 
        1) 请求方不希望等待
        2) fetchRequest不想要任何数据
        3) 已经获取到了足够的数据
        4) 获取数据出现异常
    */
    if(fetchRequest.maxWait <= 0 ||
       fetchRequest.numPartitions <= 0 ||
       bytesReadable >= fetchRequest.minBytes ||
       errorReadingData) {
     
      val response = new FetchResponse(fetchRequest.correlationId, dataRead.mapValues(_.data))
      requestChannel.sendResponse(new RequestChannel.Response(request, new FetchResponseSend(response)))
    } else {
      
      //如果没有获取到足够的数据,此时不会立即返回相应,而是采用Purgatory策略延迟相应
      val delayedFetchKeys = fetchRequest.requestInfo.keys.toSeq
      val delayedFetch = new DelayedFetch(delayedFetchKeys, request, fetchRequest.maxWait, fetchRequest,
        dataRead.mapValues(_.offset))

      
      val satisfiedByMe = fetchRequestPurgatory.checkAndMaybeWatch(delayedFetch)
      if (satisfiedByMe)
        fetchRequestPurgatory.respond(delayedFetch)
    }
  }

对于Broker Server处理FetchRequest请求的过程中,有两个重要的步骤:1)如果是来自Follower的请求,如果更新元数据信息,2)DelayedFetch是如何判断满足条件的。

对于前者主要更新以下几个方面的元数据:1)更新对于Replica的LEO(LogEndOffset),即记录该Replica的当前日志结束偏移量。2)更新Leader Replica 的HighWatermark,3)可能需要扩大ISR列表。更新完数据之后,就去检查该Broker Server上的DelayedProduce是否满足返回条件,因为发生了数据同步,导致DelayedProduce得到了更多的Replica认同,其过程如下:

private def recordFollowerLogEndOffsets(replicaId: Int, offsets: Map[TopicAndPartition, LogOffsetMetadata]) {
   
    offsets.foreach {
      case (topicAndPartition, offset) =>
        /*更新replica的的LEO,更新leader replica的highwatermark,更新isr,
          如果Leader replica的highwatermark发生变化,则unblock之前阻塞住的
        DelayedFetchRequest和DelayedProduceRequest
        */
        replicaManager.updateReplicaLEOAndPartitionHW(topicAndPartition.topic,
          topicAndPartition.partition, replicaId, offset)

       /*如果之前的ProduceRequest的ack >1,我们需要检查是否可以unblocked阻塞的 
           DelayedFetchRequest
       */
        replicaManager.unblockDelayedProduceRequests(topicAndPartition)
    }
  }

对于后者主要是判断当前是否有足够的数据可以拉取,如果有足够的数据可以拉取,则相应那些DelayedProduceRequest,其详细过程如下:

def isSatisfied(replicaManager: ReplicaManager) : Boolean = {
    var accumulatedSize = 0
    val fromFollower = fetch.isFromFollower
    partitionFetchOffsets.foreach {
      case (topicAndPartition, fetchOffset) =>
        try {
          if (fetchOffset != LogOffsetMetadata.UnknownOffsetMetadata) {
            //获取leader replica
            val replica = replicaManager.getLeaderReplicaIfLocal(topicAndPartition.topic, topicAndPartition.partition)
            /*
            如果是来自follower replica,则可以同步leader replica的LEO之前的所有数据。
            如果是来自消费者,则只同步leader replica的highwatermark之前的所有数据
            */
            val endOffset =
              if (fromFollower)
                replica.logEndOffset
              else
                replica.highWatermark

            if (endOffset.offsetOnOlderSegment(fetchOffset)) {
               /*此时代表Follower replica正在向被截取的Leader Replica拉取数据,
              
              需要立即返回response
               */
              return true
            } else if (fetchOffset.offsetOnOlderSegment(endOffset)) {
              /*此时代表Follower replica正在拉取Leader Replica旧的segment上的数据,
              
              说明follower replica已经落后太多,需要立即返回response
               */
            
              return true
            } else if (fetchOffset.precedes(endOffset)) {
              //在当前segment上,并且fetchoffset < endoffset,说明有数据可以获取
              accumulatedSize += endOffset.positionDiff(fetchOffset)
            }
          }
        } catch {
          case utpe: UnknownTopicOrPartitionException => // Case A
            return true
          case nle: NotLeaderForPartitionException =>  // Case B
            return true
        }
    }

    // 统计可以拉取的数据量是否满足要求
    accumulatedSize >= fetch.minBytes
  }

在kafka集群正常运行过程中,fetchOffset和endOffset都位于当前Segment上,因此主要统计可以fetch的数据量是否满足最小数据量的要求,只要满足,就可以响应那些DelayedFetchRequest。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值