1.大体流程
kafka 新建topic,zk和raft是两套代码有点大的区别。单节点和集群有一些细微的区别。
2.代码流程
zk集群创建topic大体流程,这里创建一个名为 flinkin-30 的主题,分区设置为2,使用zk部署的集群:
(1)客户端创建主题请求处理入口,如果当前节点是Controller则处理zk写入,否则转到到Controller进行处理。最终会执行zk写入。
KafkaApis.handle() 处理客户端请求:CREATE_TOPICS
KafkaApis.maybeForwardToController() 请求转发给控制节点
MetadataSupport.maybeForward() 请求转发 forwardingManager=None
KafkaApis.handleCreateTopicsRequest() 当前节点是否控制节点:true
ZkAdminManager.createTopics() 待创建主题集合:HashMap(flinkin-30 -> CreatableTopic(name='flinkin-30', numPartitions=2, replicationFactor=1, assignments=[], configs=[]))
ZkAdminManager.createTopics() 当前活跃broker:ArrayBuffer(BrokerMetadata(1,None))
AdminUtils.assignReplicasToBrokers() 分区副本到broker的分配计算入口
AdminUtils.assignReplicasToBrokersRackUnaware() 分区副本到broker分配计算 currentPartitionId=0,replicaBuffer=ArrayBuffer(1)
AdminUtils.assignReplicasToBrokersRackUnaware() 分区副本到broker分配计算 currentPartitionId=1,replicaBuffer=ArrayBuffer(1)
AdminZkClient.validateTopicCreate() 校验zk上是否有相同的topic,以及副本分区是否重复等等
ZkAdminManager.validateTopicCreatePolicy() 验证分配计算结果 assignments=HashMap(0 -> ArrayBuffer(1), 1 -> ArrayBuffer(1))
ZkAdminManager.maybePopulateMetadataAndConfigs()
AdminZkClient.createTopicWithAssignment() 将topic和分区分配信息写入zk入口: topic=flinkin-30,partitionReplicaAssignment=HashMap(0 -> ArrayBuffer(1), 1 -> ArrayBuffer(1))
KafkaZkClient.setOrCreateEntityConfigs() 在zk上创建路径
KafkaZkClient.createRecursive() 数据写入路径 path=/config/topics/flinkin-30
KafkaZkClient.createTopicAssignment() 写入分区信息到zk
KafkaZkClient.createRecursive() 数据写入路径 path=/brokers/topics/flinkin-30
(2)Controller节点监听到zk数据变更事件,将事件添加到Controller本地event队列中。
ZookeeperClient.ZooKeeperClientWatcher.process() 监听到zk事件:WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/brokers/topics
KafkaController.handleChildChange() 调用eventManager添加TopicChange的event
ControllerEventManager.put() 向事件队列queue中添加事件:TopicChange
(3)Controller处理事件队列中的event。
ControllerEventManager.doWork() 从事件队列queue中获取到事件:QueuedEvent(event=TopicChange, enqueueTimeMs=1676969417702)
ControllerEventManager.process() 运行事件:TopicChange
KafkaController.process() 事件类型:TopicChange
KafkaController.processTopicChange() 处理集群主题变更事件
KafkaController.onNewPartitionCreation() 处理分区创建:Set(flinkin-30-0, flinkin-30-1)
(4)Controller发现event是ISR变更事件,需要将 LEADER_AND_ISR 消息推送给其他各个节点。
ControllerChannelManager.sendRequestsToBrokers()
ControllerChannelManager.sendLeaderAndIsrRequest() 发送ISR变更消息
ReplicaStateMachine.handleStateChanges() 处理分区状态机状态转换:NewReplica
ControllerChannelManager.sendRequestsToBrokers()
ControllerChannelManager.sendLeaderAndIsrRequest() 发送ISR变更消息
ControllerChannelManager.sendRequestsToBrokers()
ControllerChannelManager.sendLeaderAndIsrRequest() 发送ISR变更消息
ControllerChannelManager.sendRequest() 集群消息添加到待发送queue中,目标brokerId=1,request.ApiKey=LEADER_AND_ISR
ControllerChannelManager.RequestSendThread.doWork() 轮训到队列中待发送请求 ApiKeys=LEADER_AND_ISR
ControllerChannelManager.sendRequest() 集群消息添加到待发送queue中,目标brokerId=1,request.ApiKey=UPDATE_METADATA
NetworkClient.doSend() 发送集群内部请求 ApiKey=LEADER_AND_ISR (org.apache.kafka.clients.NetworkClient)
ReplicaStateMachine.handleStateChanges() 处理分区状态机状态转换:OnlineReplica
ControllerChannelManager.sendRequestsToBrokers()
ControllerChannelManager.sendLeaderAndIsrRequest() 发送ISR变更消息
(5)根据前面Controller规划的分区分配方案,分配有分区副本的节点收到 LEADER_AND_ISR 请求后调用 ReplicaManager 本地创建 log 目录。
KafkaApis.handle() 处理客户端请求:LEADER_AND_ISR
KafkaApis.handleLeaderAndIsrRequest() 处理ISR列表请求
ReplicaManager.becomeLeaderOrFollower()
ReplicaManager.makeLeaders() 分区副本处理
Partition.makeLeader() 当前分区:LeaderAndIsrPartitionState(topicName='flinkin-30', partitionIndex=1, controllerEpoch=36, leader=1, leaderEpoch=0, isr=[1], zkVersion=0, replicas=[1], addingReplicas=[], removingReplicas=[], isNew=true)
Partition.updateAssignmentAndIsr() 保存分区分配信息和ISR状态
Partition.createLogIfNotExists() 创建分区log文件
Partition.createLog() 在当前broker节点初始化分区副本的本地目录
LogManager.getOrCreateLog() 当前主题分区:flinkin-30-1
LogManager.createLogDirectory() 创建日志目录:C:\workspace\kafkaData\data0
KafkaScheduler.schedule() 执行定时任务 name=PeriodicProducerExpirationCheck
Partition.makeLeader() 当前分区:LeaderAndIsrPartitionState(topicName='flinkin-30', partitionIndex=0, controllerEpoch=36, leader=1, leaderEpoch=0, isr=[1], zkVersion=0, replicas=[1], addingReplicas=[], removingReplicas=[], isNew=true)
Partition.updateAssignmentAndIsr() 保存分区分配信息和ISR状态
Partition.createLogIfNotExists() 创建分区log文件
Partition.createLog() 在当前broker节点初始化分区副本的本地目录
LogManager.getOrCreateLog() 当前主题分区:flinkin-30-0
LogManager.createLogDirectory() 创建日志目录:C:\workspace\kafkaData\data0
可以看主副本的处理过程是:
becomeLeaderOrFollower() => makeLeaders()
如果是设置副本数大于1的话还会执行从副本的处理:
becomeLeaderOrFollower() => makeFollowers()
其中创建主题时参数 replicationFactor 就指的是副本个数,如果是1那么每个分区都只有1个副本且是主副本,只有设置大于2才有高可用效果。
(6)这些分配了分区副本的节点本地创建完log目录后进行 LEADER_AND_ISR 请求的响应。
RequestHandlerHelper.sendResponse() 响应 reqApiKey=LEADER_AND_ISR
SocketServer.sendResponse() 响应客户端:Response(type=Send)
(7)Controller节点在接收到 LEADER_AND_ISR 请求的响应之后,将响应作为 event 添加到待处理队列中。
ControllerEventManager.put() 向事件队列queue中添加事件:LeaderAndIsrResponseReceived()
(8)Controller节点从 event 中将上面收到的 LEADER_AND_ISR 响应取出来,确认分区副本在各个节点处理完毕后,广播一个 UPDATE_METADATA 更新元数据请求到每个节点。
ControllerEventManager.doWork() 从事件队列queue中获取到事件:QueuedEvent(event=LeaderAndIsrResponseReceived()
ControllerChannelManager.RequestSendThread.doWork() 轮训到队列中待发送请求 ApiKeys=UPDATE_METADATA
NetworkClient.doSend() 发送集群内部请求 ApiKey=UPDATE_METADATA (org.apache.kafka.clients.NetworkClient)
ControllerEventManager.process() 运行事件:LeaderAndIsrResponseReceived()
KafkaController.process() 事件类型:LeaderAndIsrResponseReceived()
(9)其他节点再处理 UPDATE_METADATA 元数据更新请求
KafkaApis.handle() 处理客户端请求:UPDATE_METADATA
KafkaApis.handleUpdateMetadataRequest() 更新元数据:Request()
RequestHandlerHelper.sendResponseMaybeThrottleWithControllerQuota() 创建主题完成,调用sendResponse()进行响应
(10)其他节点在处理完 UPDATE_METADATA 元数据更新后进行响应。
RequestHandlerHelper.sendResponse() 响应 reqApiKey=UPDATE_METADATA
SocketServer.sendResponse() 响应客户端:Response(type=Send, request)
(11)然后Controller节点处理 UPDATE_METADATA 请求的响应。
ControllerEventManager.put() 向事件队列queue中添加事件:UpdateMetadataResponseReceived(UpdateMetadataResponseData(errorCode=0),1)
ControllerEventManager.doWork() 从事件队列queue中获取到事件:QueuedEvent(event=UpdateMetadataResponseReceived(UpdateMetadataResponseData(errorCode=0),1), enqueueTimeMs=1676969417803)
ControllerEventManager.process() 运行事件:UpdateMetadataResponseReceived(UpdateMetadataResponseData(errorCode=0),1)
KafkaController.process() 事件类型:UpdateMetadataResponseReceived(UpdateMetadataResponseData(errorCode=0),1)
(12)最终Controller节点执行最开始创建主题请求的响应。
RequestHandlerHelper.sendResponse() 响应 reqApiKey=CREATE_TOPICS
SocketServer.sendResponse() 响应客户端:Response(type=Send, request)