Kafka-broker粗粒度启动流程

一、上下文

从前面的博客,我们已经了解了Kafka的设计思想、常用命令、参数配置、示例代码。下面我们从源码的角度来看下Kafka的broker的内部细节。

二、Kafka启动

启动命令

bin/kafka-server-start.sh config/server.properties

server.properties中配置了borker所需要自定义参数

zookeeper-server-start.sh脚本启动了一个JVM进程(主类为kafka.Kafka)

bash 复制代码
if [ $# -lt 1 ];
then
	echo "USAGE: $0 [-daemon] server.properties [--override property=value]*"
	exit 1
fi
base_dir=$(dirname $0)

if [ "x$KAFKA_LOG4J_OPTS" = "x" ]; then
    export KAFKA_LOG4J_OPTS="-Dlog4j.configuration=file:$base_dir/../config/log4j.properties"
fi

if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
    export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi

EXTRA_ARGS=${EXTRA_ARGS-'-name kafkaServer -loggc'}

COMMAND=$1
case $COMMAND in
  -daemon)
    EXTRA_ARGS="-daemon "$EXTRA_ARGS
    shift
    ;;
  *)
    ;;
esac

exec $base_dir/kafka-run-class.sh $EXTRA_ARGS kafka.Kafka "$@"

三、Kafka源码

1、kafka.Kafka

Scala 复制代码
object Kafka extends Logging {

  def main(args: Array[String]): Unit = {
    try {
      //获取参数
      val serverProps = getPropsFromArgs(args)
      //构建KafkaServer(如果没有配置zookeeper角色)或者KafkaRaftServer
      val server = buildServer(serverProps)

      //附加关机处理程序以捕获终止信号以及正常终止
      Exit.addShutdownHook("kafka-shutdown-hook", {...}

      //启动KavkaServer
      server.startup()
      server.awaitShutdown()
    }
    Exit.exit(0)
  }

}

2、KafkaServer

Scala 复制代码
object KafkaServer {

}

//表示单个Kafka broker 的生命周期。处理启动和关闭单个Kafka节点所需的所有功能。
class KafkaServer(...)extends KafkaBroker with Server {

  override def startup(): Unit = {

    _brokerState = BrokerState.STARTING
    //设置zookeeper
    initZkClient(time)
    configRepository = new ZkConfigRepository(new AdminZkClient(zkClient))
    //获取或者创建cluster_id
    _clusterId = getOrGenerateClusterId(zkClient)
    //加载 元数据
    val initialMetaPropsEnsemble = {...}
    //生成brokerId
    config.brokerId = getOrGenerateBrokerId(initialMetaPropsEnsemble)
    //从ZooKeeper初始化动态代理配置。在此之后进行的任何更新都将在ZkConfigManager启动后应用。
    config.dynamicConfig.initialize(Some(zkClient), clientMetricsReceiverPluginOpt = None)
    //启动调度器
    kafkaScheduler = new KafkaScheduler(config.backgroundThreads)
    kafkaScheduler.startup()
    //创建和配置指标metrics
    createCurrentControllerIdMetric()
    //LogDirFailureChannel允许外部线程阻止等待新的脱机日志目录
    logDirFailureChannel = new LogDirFailureChannel(config.logDirs.size)
    //确保所有存储目录都有meta.properties文件。
    val metaPropsEnsemble = {...}
    //启动日志管理
    _logManager = LogManager(...)
    logManager.startup(zkClient.getAllTopicsInCluster())
    //创建远程日志管理器
    //它主要负责:
    //---初始化"RemoteStorageManager"和"RemoteLogMetadataManager"实例
    //---接收任何leader和follower副本事件以及分区停止事件,并对其采取行动
    //---还提供API来获取关于远程日志段的索引和元数据
    //---将日志段复制到远程存储
    //---根据保留大小或保留时间清理过期的段
    remoteLogManagerOpt = createRemoteLogManager()
    //每个分区的状态(例如,当前leader)的缓存。此缓存通过Contrllor的UpdateMetadataRequest进行更新。每个broker异步维护相同的缓存。
    metadataCache = MetadataCache.zkMetadataCache(...)
    val controllerNodeProvider = new MetadataCacheControllerNodeProvider(metadataCache, config,
          () => Option(quorumControllerNodeProvider).map(_.getControllerInfo()))
    //初始化功能更改侦听器
    _featureChangeListener = new FinalizedFeatureChangeListener(metadataCache, _zkClient)
    //为所有SCRAM机制启用委托令牌缓存,以简化动态更新。如果动态启用新的SCRAM机制,这将使缓存保持最新。
    tokenCache = new DelegationTokenCache(ScramMechanism.mechanismNames)
    credentialProvider = new CredentialProvider(ScramMechanism.mechanismNames, tokenCache)
    //此类管理broker和Controller之间的连接。
    //它运行一个[[NodeToControllerRequestThread]],该线程使用broker的元数据缓存作为自己的元数据来查找并连接到Controller。
    //通道是异步的,在后台运行网络连接。飞行中请求的最大数量设置为1,以确保控制器的有序响应,
    //因此必须注意不要长时间阻止未完成的请求。
    clientToControllerChannelManager = new NodeToControllerChannelManagerImpl(...)
    clientToControllerChannelManager.start()
    //启动转发管理器
    var autoTopicCreationChannel = Option.empty[NodeToControllerChannelManager]
    if (enableForwarding) {
      this.forwardingManager = Some(ForwardingManager(clientToControllerChannelManager))
      autoTopicCreationChannel = Some(clientToControllerChannelManager)
    }
    //DefaultApiVersionManager
    //默认的ApiVersionManager支持转发并具有元数据缓存,用于broker和zk控制器。启用转发时,启用的api由代理侦听器类型和控制器api决定,否则启用的api则由代理侦听程序类型决定,这与SimpleApiVersionManager相同。
    val apiVersionManager = ApiVersionManager(...)
    //创建并启动socket服务器接收器线程,以便知道绑定的端口。将启动处理器延迟到初始化序列结束,以确保在处理身份验证之前已加载凭据。
    //请注意,我们允许在启用转发时使用KRaft模式控制器API,以便暴露Envelope请求。目前仅用于测试。
    //负责处理与broker之间的新连接、请求和响应
    //Kafka支持两种类型的请求计划:data-plane 和 control-plane
    socketServer = new SocketServer(config, metrics, time, credentialProvider, apiVersionManager)
    //根据IBP版本启动更改分区管理器
    alterPartitionManager = if(config.interBrokerProtocolVersion.isAlterPartitionSupported) {
        //通过向控制器发送AlterPartition请求(从2.7开始)或直接更新ZK(从2.7之前开始)来处理ISR的更新。更新ISR是一个异步操作,因此分区将通过回调了解其请求的结果。
        //请注意,ISR状态更改仍然可以由控制器发起,并通过LeaderAndIsr请求发送到分区
        AlterPartitionManager(...)
    } else {
        AlterPartitionManager(kafkaScheduler, time, zkClient)
    }
    alterPartitionManager.start()
    //启动副本管理
    _replicaManager = createReplicaManager(isShuttingDown)
    replicaManager.startup()
    
    //创建broker信息(ip、端口、机架等信息) ,并将其注册到zookeeper中
    val brokerInfo = createBrokerInfo
    val brokerEpoch = zkClient.registerBroker(brokerInfo)

    //启动 token 管理器
    //使用Zk作为元数据时,缓存委托令牌。这包括Zk对委托令牌的其他特定处理。
    tokenManager = new DelegationTokenManagerZk(config, tokenCache, time , zkClient)
    tokenManager.startup()

    //启动 kafka Controller
    _kafkaController = new KafkaController(config, zkClient, time, metrics, brokerInfo, brokerEpoch, tokenManager, brokerFeatures, metadataCache, threadNamePrefix)
    kafkaController.startup()

    //启动ZooKeeper迁移的其他组件
    //zookeeper.metadata.migration.enable   ZK到KRaft迁移配置 
    if (config.migrationEnabled) {
      //...
    }
    //由ZK brokers 在KRaft迁移期间使用。当与KRaft控制器交谈时,我们需要使用BrokerLifecycleManager中的epoch,而不是ZK(通过KafkaController)
    brokerEpochManager = new ZkBrokerEpochManager(metadataCache, kafkaController, Option(lifecycleManager))
    //它负责创建topic、分区操作
    adminManager = new ZkAdminManager(config, metrics, metadataCache, zkClient)
    //启动组协调员,,目前是硬编码
    //GroupCoordinatorAdapter是kafka.coordinator.group.GroupCoordinator的一个薄薄的包装
    //GroupCoordinator 负责处理一般的集团成员资格和offset管理。
    //每个Kafka服务器都实例化一个组协调器,负责一组。根据组名将组分配给协调员。
    //GroupCoordinator中的延迟操作使用"group"作为延迟操作锁。
    //ReplicaManager.appendRecords可能会在持有其回调使用的组锁时被调用。延迟回调可以获取组锁,因为只有当可以获取组锁定时,延迟操作才完成。
    groupCoordinator = GroupCoordinatorAdapter(...)
    groupCoordinator.startup(() => zkClient.getTopicPartitionCount(Topic.GROUP_METADATA_TOPIC_NAME).getOrElse(config.offsetsTopicPartitions))
    //创建生产者ID管理器
    //ProducerIdManager是事务协调器的一部分,它以独特的方式提供ProducerIds,
    //这样同一个producerId就不会在多个事务协调器之间分配两次。
    val producerIdManager = if (config.interBrokerProtocolVersion.isAllocateProducerIdsSupported) {
          ProducerIdManager.rpc(
            config.brokerId,
            time,
            brokerEpochSupplier = brokerEpochSupplier,
            clientToControllerChannelManager
          )
        } else {
          ProducerIdManager.zk(config.brokerId, zkClient)
        }
    //启动事务协调器,具有单独的后台线程调度程序,用于事务过期和日志加载
    transactionCoordinator = TransactionCoordinator(...)
    transactionCoordinator.startup(...)
    //启动自动主题创建管理器
    this.autoTopicCreationManager = AutoTopicCreationManager(...)
    //获取授权人,如果指定了授权人,则对其进行初始化
    authorizer = config.createNewAuthorizer()
    val sessionIdRange = Int.MaxValue / NumFetchSessionCacheShards
    val fetchSessionCacheShards =(...)
    val fetchManager = new FetchManager(Time.SYSTEM, new FetchSessionCache(fetchSessionCacheShards))
    //在代理开始处理请求之前启动RemoteLogManager。
    remoteLogManagerOpt.foreach { rlm =>
      //...
      rlm.startup()
    }
    //开始处理请求
    val zkSupport = ZkSupport(adminManager, kafkaController, zkClient, forwardingManager, metadataCache, brokerEpochManager)
    //KafkaApis中有处理各种Kafka请求的逻辑,例如:
    //PRODUCE、FETCH、LIST_OFFSETS、OFFSET_COMMIT、CREATE_TOPICS等等
    def createKafkaApis(requestChannel: RequestChannel): KafkaApis = new KafkaApis(...)
    dataPlaneRequestProcessor = createKafkaApis(socketServer.dataPlaneRequestChannel)
    //处理请求线程池
    //会启动num.io.threads(默认8个)个线程
    //每个线程会创建一个 KafkaRequestHandler
    dataPlaneRequestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.dataPlaneRequestChannel, dataPlaneRequestProcessor, time,
          config.numIoThreads, s"${DataPlaneAcceptor.MetricPrefix}RequestHandlerAvgIdlePercent", DataPlaneAcceptor.ThreadPrefix)
    //创建等待和Controller建立连接的端点
    socketServer.controlPlaneRequestChannelOpt.foreach { controlPlaneRequestChannel =>
          controlPlaneRequestProcessor = createKafkaApis(controlPlaneRequestChannel)
          controlPlaneRequestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.controlPlaneRequestChannelOpt.get, controlPlaneRequestProcessor, time,
            1, s"${ControlPlaneAcceptor.MetricPrefix}RequestHandlerAvgIdlePercent", ControlPlaneAcceptor.ThreadPrefix)
        }
    //启动动态配置管理器
    dynamicConfigHandlers = Map[String, ConfigHandler](ConfigType.TOPIC -> new TopicConfigHandler(replicaManager, config, quotaManagers, Some(kafkaController)),
                                                           ConfigType.CLIENT -> new ClientIdConfigHandler(quotaManagers),
                                                           ConfigType.USER -> new UserConfigHandler(quotaManagers, credentialProvider),
                                                           ConfigType.BROKER -> new BrokerConfigHandler(config, quotaManagers),
                                                           ConfigType.IP -> new IpConfigHandler(socketServer.connectionQuotas))
    //创建配置管理器。开始监听通知
    dynamicConfigManager = new ZkConfigManager(zkClient, dynamicConfigHandlers)
    dynamicConfigManager.startup()
    //此方法启用此SocketServer管理的所有端点的请求处理。一旦关联的未来完成,每个端点都将异步启动。
    //因此,我们不知道在这个函数结束时是否有任何特定的请求处理器正在运行,只知道它可能正在运行。
    val enableRequestProcessingFuture = socketServer.enableRequestProcessing(authorizerFutures)
    //在这里封锁,直到所有授权人futures完成
    //开始处理授权人futures
    CompletableFuture.allOf(authorizerFutures.values.toSeq: _*).join()
    //结束处理授权人futures
    //等待所有SocketServer端口打开,并启动Accepters。
    //开始处理启用请求处理future
    enableRequestProcessingFuture.join()
    //结束处理启用请求处理future
    //KafkaServer启动成功
    _brokerState = BrokerState.RUNNING
    AppInfoParser.registerAppInfo(Server.MetricsPrefix, config.brokerId.toString, metrics, time.milliseconds())
  }

}

四、总结

1、将BrokerState置成 STARTING

2、启动 ZkClient

3、创建或获取cluster_id

4、加载元数据

5、生成brokerId

6、启动KafkaScheduler

7、配置并启动指标类服务

8、确保所有存储目录都有meta.properties文件,并对其中的数据进行校验

9、启动LogManager(负责日志的创建、检索和清理。所有读写操作都委托给各个日志实例,后台线程会通过定期截断多余的日志段来处理日志保留)

10、创建RemoteLogManager:

a、接初始化"RemoteStorageManager"和"RemoteLogMetadataManager"实例

b、接收任何领导者和追随者副本事件以及分区停止事件,并对其采取行动

c、还提供API来获取关于远程日志段的索引和元数据

d、将日志段复制到远程存储

e、根据保留大小或保留时间清理过期的段

11、对元数据进行缓存

12、缓存token

13、启动NodeToControllerChannelManagerImpl(broker与controller的通信线程)

14、启动转发管理器

15、创建SocketServer(负责处理与broker之间的新连接、请求和响应,支持两种请求:data-plane和control-plane)

16、根据IBP版本启动更改分区管理器

17、启动replicaManager(副本管理器)

18、使用zkClient将broker信息向zookeeper注册

19、启动tokenManager

20、启动KafkaController

21、启动ZkAdminManager(可以使用它进行topic、分区的创建)

22、启动groupCoordinator(组协调器)

23、启动ProducerIdManager(生产者ID管理器)

24、启动TransactionCoordinator(事务协调器)

25、启动AutoTopicCreationManager(自动主题创建管理器)

26、启动KafkaApis(给外部提供统一的api以及对应的处理逻辑,例如:PRODUCE、OFFSET_COMMIT)

27、创建并启动数据侧KafkaRequestHandlerPool(kafka使用多线程来处理请求,默认8个线程)

28、创建并启动控制侧KafkaRequestHandlerPool

29、创建配置管理器。开始监听通知

30、将BrokerState置成 RUNNING

相关推荐
小刘爱喇石( ˝ᗢ̈˝ )16 分钟前
行式数据库与列式数据库区别
数据库·分布式
用户Taobaoapi201436 分钟前
淘宝商品列表查询 API 接口详解
大数据
MiniFlyZt1 小时前
消息队列MQ(RabbitMQ)
spring boot·分布式·微服务·rabbitmq
涛思数据(TDengine)1 小时前
taosd 写入与查询场景下压缩解压及加密解密的 CPU 占用分析
大数据·数据库·时序数据库·tdengine
DuDuTalk1 小时前
DuDuTalk接入DeepSeek,重构企业沟通数字化新范式
大数据·人工智能
大数据追光猿2 小时前
Qwen 模型与 LlamaFactory 结合训练详细步骤教程
大数据·人工智能·深度学习·计算机视觉·语言模型
梦城忆2 小时前
常用的分布式 ID 设计方案
分布式
qxlxi2 小时前
【分布式】聊聊分布式id实现方案和生产经验
分布式·架构
Elastic 中国社区官方博客2 小时前
使用 Elastic-Agent 或 Beats 将 Journald 中的 syslog 和 auth 日志导入 Elastic Stack
大数据·linux·服务器·elasticsearch·搜索引擎·信息可视化·debian
&星辰入梦来&3 小时前
RabbitMQ 从入门到精通
分布式·rabbitmq