Kafka-broker粗粒度启动流程

一、上下文

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

二、Kafka启动

启动命令

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

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

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

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

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

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

你提到的 `kafka-broker1`、`kafka-broker2`、`kafka-broker3` 是你为 Kafka 集群中的三台 broker 主机配置的别名(host alias),这些别名是你在本地 `/etc/hosts` 文件中定义的,用于将 IP 地址映射为主机名,方便 Kerberos 认证和 Kafka 配置。 --- ## 🧩 问题:`kafka-broker1` 怎么获取? 你问的 `kafka-broker1` 是你本地定义的主机名(别名),它本身不是“获取”的对象,而是你为 Kafka broker 的 IP 地址 `25.45.48.105` 所起的主机名。它的作用是: - 用于 Kerberos 认证(principal 通常为 `kafka/kafka-broker1@REALM`) - 用于 Kafka 客户端连接 Kafka broker - 用于服务间通信时使用主机名而非 IP --- ## ✅ 如何确保 `kafka-broker1` 可用? ### 1. 确保 `/etc/hosts` 中已配置 在你的客户端机器(运行 Kafka 消费者或生产者的机器)上,确保 `/etc/hosts` 文件中包含如下内容: ```bash 25.45.48.105 kafka-broker1 25.45.48.191 kafka-broker2 25.45.49.19 kafka-broker3 ``` > ✅ **注意:** 如果你是在 Windows 上开发,编辑的是 `C:\Windows\System32\drivers\etc\hosts` 文件。 --- ### 2. 验证 `kafka-broker1` 是否能解析 你可以使用以下命令验证主机名是否解析正确: #### Linux / Mac: ```bash ping kafka-broker1 nslookup kafka-broker1 ``` #### Windows: ```cmd ping kafka-broker1 nslookup kafka-broker1 ``` 如果能成功解析到 `25.45.48.105`,说明配置成功。 --- ## ✅ 如何获取 Kafka broker 的 `kafka-broker1` 的 Kerberos principal? Kafka broker 的 principal 通常形如: ``` kafka/kafka-broker1@HADOOP.COM ``` 你需要从 Kerberos KDC 管理员处获取该 principal 的 keytab 文件,或者使用以下命令在 KDC 上创建: ```bash # 在 KDC 服务器上执行 ktutil add -p sh_sjzt_dwzy@HADOOP.COM -k kafka-broker1.keytab ``` 然后将该 keytab 文件拷贝到 Kafka broker 节点上,并在 Kafka broker 的配置中指定: ```properties sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \ useKeyTab=true \ storeKey=true \ keyTab="/path/to/kafka.keytab" \ principal="kafka/kafka-broker1@HADOOP.COM"; ``` --- ## ✅ Kafka broker 如何配置 `kafka-broker1` 的主机名? 在 Kafka broker 的 `server.properties` 文件中,你需要确保以下配置项使用了主机名而不是 IP: ```properties # Kafka broker 的监听地址 listeners=PLAINTEXT://kafka-broker1:21007 # 对外广播的地址(其他客户端连接时使用) advertised.listeners=PLAINTEXT://kafka-broker1:21007 ``` > ⚠️ 如果你使用 IP 地址而不是主机名,Kerberos 认证将失败。 --- ## ✅ Kafka 客户端如何使用 `kafka-broker1` 在 Kafka 客户端(消费者或生产者)配置中,使用如下方式连接: ```yaml mrs: kafka: bootstrap-servers: kafka-broker1:21007,kafka-broker2:21007,kafka-broker3:21007 security-protocol: SASL_PLAINTEXT sasl-kerberos-service-name: kafka ``` 并在 JAAS 配置中指定你的用户 principal 和 keytab 文件: ```java props.put("sasl.jaas.config", "com.sun.security.auth.module.Krb5LoginModule required " + "useKeyTab=true " + "storeKey=true " + "keyTab=\"/path/to/user.keytab\" " + "principal=\"sh_sjzt_dwzy@HADOOP.COM\";"); ``` --- ## ✅ 示例:完整的 Kafka 消费者 Java 代码(使用主机名) ```java import org.apache.kafka.clients.consumer.*; import org.apache.kafka.common.serialization.StringDeserializer; import java.time.Duration; import java.util.Collections; import java.util.Properties; public class KafkaConsumerExample { public static void main(String[] args) { Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-broker1:21007"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); // Kerberos 配置 props.put("security.protocol", "SASL_PLAINTEXT"); props.put("sasl.kerberos.service.name", "kafka"); props.put("sasl.jaas.config", "com.sun.security.auth.module.Krb5LoginModule required " + "useKeyTab=true " + "storeKey=true " + "keyTab=\"/path/to/user.keytab\" " + "principal=\"sh_sjzt_dwzy@HADOOP.COM\";"); Consumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Collections.singletonList("xx")); try { while (true) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) { System.out.printf("Received: %s (Topic: %s, Partition: %d, Offset: %d)%n", record.value(), record.topic(), record.partition(), record.offset()); } } } finally { consumer.close(); } } } ``` --- ## ✅ 总结 | 目标 | 操作 | |------|------| | 使用 `kafka-broker1` | 在 `/etc/hosts` 中配置 IP 到主机名的映射 | | 验证主机名是否可用 | 使用 `ping`, `nslookup`, `dig` | | 获取 Kafka broker 的 principal | 从 KDC 管理员获取 `kafka/kafka-broker1@REALM` 的 keytab | | Kafka broker 配置 | 使用主机名配置 `listeners` 和 `advertised.listeners` | | Kafka 客户端配置 | 使用主机名连接 Kafka,配置 Kerberos JAAS | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值