kafka源码分析之kafkaApis

本文深入探讨 KafkaApis 的核心功能,包括请求处理池的构造与工作原理,详细解析了如何处理网络请求,特别是元数据更新请求的处理流程。此外,还介绍了分区 Leader 和 ISR 变更的处理,以及 Consumer 的 Offset 提交。通过对 KafkaApis 的源码分析,读者可以深入了解 Kafka 内部的消息处理机制。
摘要由CSDN通过智能技术生成

KafkaApis

说明:用于处理对kafka的消息请求的中心转发组件,kafkaapis需要依赖于如下几个组件:

apis = new KafkaApis(socketServer.requestChannelreplicaManager

  consumerCoordinator,
  kafkaControllerzkUtilsconfig.brokerIdconfigmetadataCachemetrics,  

  authorizer)

 

其最核心的处理主要由KafkaApis中的handle函数进行调度.

 

请求处理池

在KafkaApis实例生成后,会同时生成一个KafkaRequestHandlerPool实例.

这个实例主要用于对kafka的请求进行处理的实例,需要依赖如下几个组件与配置:

配置项num.io.threads,默认值8,用于处理IO操作的线程个数.

requestHandlerPool new KafkaRequestHandlerPool(config.brokerId,

  socketServer.requestChannelapisconfig.numIoThreads)

 

这里会根据io的线程个数,生成对应的处理线程KafkaRequestHandler.

this.logIdent "[Kafka Request Handler on Broker " + brokerId + "], "
val 
threads new Array[Thread](numThreads)
val runnables new Array[KafkaRequestHandler](numThreads)
for(i <- until numThreads) {
  runnables(i) = new KafkaRequestHandler(ibrokerIdaggregateIdleMeternumThreadsrequestChannelapis)
  threads(i) = Utils.daemonThread("kafka-request-handler-" + irunnables(i))
  threads(i).start()
}

 

接下来看看KafkaRequestHandler线程:

def run() {
  while(true) {
    try {

这里从请求队列中,取出一个请求,直接交给KafkaApis进行处理.
      var req : RequestChannel.Request = null
      while 
(req == null) {
        // We use a single meter for aggregate idle percentage for the thread pool.
        // Since meter is calculated as total_recorded_value / time_window and
        // time_window is independent of the number of threads, each recorded idle
        // time should be discounted by # threads.
        
val startSelectTime = SystemTime.nanoseconds
        
req = requestChannel.receiveRequest(300)
        val idleTime = SystemTime.nanoseconds - startSelectTime
        aggregateIdleMeter.mark(idleTime / totalHandlerThreads)
      }

      if(req eq RequestChannel.AllDone) {
        debug("Kafka request handler %d on broker %d received shut down 

          command".format(
          idbrokerId))
        return
      
}
      req.requestDequeueTimeMs = SystemTime.milliseconds
      
trace("Kafka request handler %d on broker %d handling request %s".format(id

           brokerIdreq))
      apis.handle(req)
    } catch {
      case e: Throwable => error("Exception when handling request"e)
    }
  }
}

 

对网络请求进行处理

这个部分通过KafkaApis中的handle函数进行处理,并根据不同的请求路由进行不同的处理.

处理metadata更新请求

当某个partition发生变化后,会通过生成UpdateMetadataRequest请求向所有的brokers发送这个请求,也就是说每一个活着的broker都会接受到metadata变化的请求,并对请求进行处理.

这个处理在partition的状态发生变化,partition重新分配,broker的启动与停止时,会发起update metadata的请求.

入口通过KafkaApis中的handle函数

case RequestKeys.UpdateMetadataKey => handleUpdateMetadataRequest(request)

 

接下来看看handleUpdateMetadataRequest的函数处理流程:


def handleUpdateMetadataRequest(request: RequestChannel.Request) {
  val updateMetadataRequest = 

        request.requestObj.asInstanceOf[UpdateMetadataRequest]

首先检查当前的用户是否有ClusterAction操作的权限,如果有接着执行下面的流程。
  authorizeClusterAction(request)

根据请求的metadata的更新消息,更新对memtadataCache中的内容。这个包含有broker的添加与删除,partition的状态更新等。
  replicaManager.maybeUpdateMetadataCache(updateMetadataRequestmetadataCache)

  val updateMetadataResponse = new UpdateMetadataResponse(

              updateMetadataRequest.correlationId)


  requestChannel.sendResponse(new Response(request

     new RequestOrResponseSend(request.connectionIdupdateMetadataResponse)))
}

 

看看ReplicaManager中处理对更新metadata的请求的流程:

在副本管理组件中,直接通过MetadataCache中的updateCache函数对请求过来的消息进行处理,用于更新当前的broker中的cache信息。

更新cache的流程:

1,更新cache中用于存储所有的broker节点的aliveBrokers集合。

2,对请求过来的修改过状态的partition的集合进行迭代,

2,1,如果partition的leader的节点被标记为-2,表示这是一个被删除的partition,从cache集合中找到这个partition对应的topic的子集合,并从这个集合中移出这个partition,如果这个topic中已经不在包含partition时,从cache中直接移出掉这个topic.

2,2,这种情况下,表示是对partition的状态的修改,包含partition的副本信息,与partition的leader的isr的信息,直接更新cache集合中topic子集合中对应此partition的状态信息。


def maybeUpdateMetadataCache(updateMetadataRequest: UpdateMetadataRequest

          metadataCache: MetadataCache) {
  replicaStateChangeLock synchronized {
    if(updateMetadataRequest.controllerEpoch < controllerEpoch) {
      val stateControllerEpochErrorMessage = ("Broker %d received update metadata 

        request with correlation id %d from an " +
        "old controller %d with epoch %d. Latest known controller epoch is %d")

        .format(localBrokerId,
        updateMetadataRequest.correlationIdupdateMetadataRequest.controllerId

        updateMetadataRequest.controllerEpoch,
        controllerEpoch)
      stateChangeLogger.warn(stateControllerEpochErrorMessage)
      throw new ControllerMovedException(stateControllerEpochErrorMessage)
    } else {
      metadataCache.updateCache(updateMetadataRequestlocalBrokerId

          stateChangeLogger)
      controllerEpoch = updateMetadataRequest.controllerEpoch
    }
  }
}

处理partitionLeaderAndIsr请求

这个请求主要是针对partitionleader或者isr发生变化后的请求处理.这个接收请求的broker节点一定会是包含有对应的partition的副本的节点才会被接收到数据.

case RequestKeys.LeaderAndIsrKey => handleLeaderAndIsrRequest(request)

 

接下来看看handleLeaderAndIsrRequest的处理流程:

def handleLeaderAndIsrRequest(request: RequestChannel.Request) {

首先先得到请求的内容.针对一个LeaderAndIsr的请求,得到的请求内容是一个LeaderAndIsrRequest的实例.
  
val leaderAndIsrRequest = request.requestObj.asInstanceOf[LeaderAndIsrRequest]

检查当前的请求用户是否具备ClusterAction操作的权限.
  authorizeClusterAction(request)

  try {

这个函数用于在partition的isr被改变后,对成为leader的副本与成为follower的副本判断这个副本对应的topic是否是内置的__consumer_offsets topic,通过GroupMetadataManager中的对应函数来处理内置的topic的leader上线与下线的操作.
    def onLeadershipChange(updatedLeaders: Iterable[Partition],

            updatedFollowers: Iterable[Partition]) {
      
updatedLeaders.foreach { partition =>
        if (partition.topic == GroupCoordinator.GroupMetadataTopicName)
          coordinator.handleGroupImmigration(partition.partitionId)
      }
      updatedFollowers.foreach { partition =>
        if (partition.topic == GroupCoordinator.GroupMetadataTopicName)
          coordinator.handleGroupEmigration(partition.partitionId)
      }
    }

根据请求的partition,通过副本管理组件来对partition进行leader或者follower的选择.
    // call replica manager to handle updating partitions to become leader or follower
    
val result = replicaManager.becomeLeaderOrFollower(leaderAndIsrRequest

           metadataCacheonLeadershipChange)
    val leaderAndIsrResponse = new LeaderAndIsrResponse(

           leaderAndIsrRequest.correlationId,

           result.responseMapresult.errorCode)

 

生成操作成功后的返回结果,并向请求方进行响应.
    requestChannel.sendResponse(new Response(request,

           new RequestOrResponseSend(request.connectionIdleaderAndIsrResponse)))


  } catch {
    case e: KafkaStorageException =>
      fatal("Disk error during leadership change."e)
      Runtime.getRuntime.halt(1)
  }
}

 

ReplicaManager中的becomeLeaderOrFollower函数:

这个函数用于判断指定的partition是应该成为leader还是应该成为follower.
def becomeLeaderOrFollower(leaderAndISRRequest: LeaderAndIsrRequest,
                           metadataCache: MetadataCache,
                           onLeadershipChange: (Iterable[Partition]

                   Iterable[Partition]) => Unit): BecomeLeaderOrFollowerResult = {


  leaderAndISRRequest.partitionStateInfos.foreach { case (

(topicpartition)stateInfo) =>
      stateChangeLogger.trace("日志)
  }


  replicaStateChangeLock synchronized {
    val responseMap = new mutable.HashMap[(String, Int), Short]

 

如果当前请求的epoch的值小于当前controllerEpoch的值,打印warn的日志,

并返回StaleControllerEpochCode错误代码.
    if (leaderAndISRRequest.controllerEpoch < controllerEpoch) {
      leaderAndISRRequest.partitionStateInfos.foreach { 

    case ((topicpartition)stateInfo) =>
          stateChangeLogger.warn(("日志)
      }


      BecomeLeaderOrFollowerResult(responseMap

        ErrorMapping.StaleControllerEpochCode)
    } else {

这里得到当前请求的最新的epoch的值,并设置当前的broker的epoch的值为请求的值,
      val controllerId = leaderAndISRRequest.controllerId
      val correlationId = leaderAndISRRequest.correlationId
      controllerEpoch = leaderAndISRRequest.controllerEpoch

对请求的所有的partition进行迭代,并对partition的状态进行检查.
      // First check partition's leader epoch
      
val partitionState = new mutable.HashMap[PartitionPartitionStateInfo]()
      leaderAndISRRequest.partitionStateInfos.foreach {

    case ((topicpartitionId)partitionStateInfo) =>

这里通过getOrCreatePartition从allPartitions集合中得到这个partition的实例,如果这个partition的实例在集合中不存在时,会创建这个实例.
          val partition = getOrCreatePartition(topicpartitionId)
          val partitionLeaderEpoch = partition.getLeaderEpoch()

 

检查当前的partition中的leaderEpoch的值是否小于新请求的值,如果小于这个值,同时这个partition对应的副本包含有当前的broker时,把这个partition与状态添加到partitionState的集合中,否则表示当前的broker中不包含这个partition的副本,打印一个日志,并在responseMap中记录这个partition的error code为UnknownTopicOrPartitionCode.如果partition的leaderEpoch的值大于或等于请求的epoch的值,打印日志,并在responseMap中添加这个partition的error code的值为StaleLeaderEpochCode.
          
if (partitionLeaderEpoch < partitionStateInfo.

             leaderIsrAndControllerEpoch.leaderAndIsr.leaderEpoch) {
            if(partitionStateInfo.allReplicas.contains(config.brokerId))
              partitionState.put(partitionpartitionStateInfo)
            else {
              stateChangeLogger.warn(("日志)


              responseMap.put((topicpartitionId),               

             ErrorMapping.UnknownTopicOrPartitionCode)
            }
          } else {
            // Otherwise record the error code in response
            stateChangeLogger
.warn(("日志)
            responseMap.put((topicpartitionId)ErrorMapping.StaleLeaderEpochCode)
          }
      }


这里根据partition的副本包含有当前的broker节点的所有的partition的集合,得到这个partition的leader是当前的broker的所有的partition的集合,同时得到包含有当前的broker的副本的partition中,leader不是当前的broker的所有的partition的集合.
      val partitionsTobeLeader = partitionState.filter {

    case (partitionpartitionStateInfo) =>
           partitionStateInfo.leaderIsrAndControllerEpoch.leaderAndIsr.leader == 

             config.brokerId
      
}
      val partitionsToBeFollower = (partitionState -- partitionsTobeLeader.keys)

如果partitions需要被切换成leader的集合不为空,对这些需要在当前的broker中的partition执行leader操作的集合执行makeLeaders函数.这里得到的集合是partition中当前broker被搞成leader的partition集合,
      val partitionsBecomeLeader = if (!partitionsTobeLeader.isEmpty)
        makeLeaders(controllerIdcontrollerEpochpartitionsTobeLeader

        leaderAndISRRequest.correlationIdresponseMap)
      else
        
Set.empty[Partition]

 

如果partitions需要被切换成follower的集合不为空,对这些需要在当前的broker中的partition执行follower操作的集合执行makeFollowers函数.这里得到的集合是partition中当前broker被搞成follower的partition集合,
      val partitionsBecomeFollower = if (!partitionsToBeFollower.isEmpty)
        makeFollowers(controllerIdcontrollerEpochpartitionsToBeFollower

      leaderAndISRRequest.correlationIdresponseMapmetadataCache)
      else
        
Set.empty[Partition]

 

更新每个partition中最后一个offset的值到日志目录下的checkpoint文件中.
      
if (!hwThreadInitialized) {
        startHighWaterMarksCheckPointThread()
        hwThreadInitialized true
      
}

停止掉没有partition引用的fetcher的线程.这个线程用于对partition的消息的同步,从leader的partition中同步数据到follower中.
      replicaFetcherManager.shutdownIdleFetcherThreads()

这里根据当前节点是leader的partition集合与当前节点变成follower的partition集合,检查这些partition对应的topic是否是__consumer_offsets topic,这个topic用来记录每个consumer对应的消费的offset的信息,如果是这个topic的partition时,根据leader与follower的集合,通过GroupMetadataManager实例对两个集合分别执行partition的leader的上线与下线的操作.
      onLeadershipChange(partitionsBecomeLeaderpartitionsBecomeFollower)
      BecomeLeaderOrFollowerResult(responseMapErrorMapping.NoError)
    }
  }
}

 

Partitionleader设置

在LeaderAndIsr的请求过来时,如果请求的消息中对应的partition的leader是当前的broker节点时,会根据这个partitions的集合执行ReplicaManager.makeLeaders的操作,

 */
private def makeLeaders(controllerId: Int,
                        epoch: Int,
                        partitionState: Map[PartitionPartitionStateInfo],
                        correlationId: Int,
                        responseMap: mutable.Map[(String, Int)

Kafka 0.9.0 是一个分布式流处理平台,用于高效地处理和传输大规模数据流。它采用了发布-订阅的消息队列模型,能够以高吞吐量、低延迟的方式处理数据。 在源码分析上,我可以给你一些大致的指导,但是详细的分析可能需要具体的问题或主题。以下是一些主要的模块和功能,你可以根据需要选择感兴趣的部分进行深入研究: 1. Broker:Kafka 的核心组件,负责接收、存储和转发消息。可以了解 Broker 的启动过程、消息存储机制和网络通信模块等。 2. Producer:负责向 Kafka 集群发送消息。可以了解 Producer 的消息发送流程、消息分区机制和消息确认机制等。 3. Consumer:负责从 Kafka 集群消费消息。可以了解 Consumer 的消息订阅机制、消费者组管理和消息位移管理等。 4. Topic 和 Partition:Kafka 将消息分为多个 Topic,并将每个 Topic 划分为多个 Partition。可以了解 Topic 和 Partition 的创建、分配和管理等。 5. Replica 和 Leader-Follower:Kafka 使用副本机制来保证数据的可靠性。可以了解 Replica 的同步和选举机制,以及 Leader-Follower 模型的实现细节。 6. ZooKeeper:Kafka 使用 ZooKeeper 来进行集群的协调和管理。可以了解 Kafka 对 ZooKeeper 的依赖和使用方式。 以上只是一些主要的模块和功能,Kafka源码非常庞大和复杂,涉及到很多细节和算法。如果你有具体的问题或感兴趣的主题,我可以提供更详细的指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值