Kafka Rebalance 机制和选举策略总结
Kafka 作为大数据领域常用的消息中间件,其核心原理相对于其它消息中间件而言更为复杂,本文主要介绍 Kafka 消费者的 rebalance 机制以及 controller broker 选举机制、副本选举机制等实现原理。
Kafka 简易拓扑结构
Kafka 核心总控制器 Controller
在 Kafka 集群中会有一个或者多个 broker,其中有一个 broker 会被选举为控制器(Kafka Controller),它负责管理整个集群中所有分区和副本的状态。
- 当某个分区的 leader 副本出现故障时,由控制器负责为该分区选举新的 leader 副本。
- 当检测到某个分区的 ISR 集合发生变化时,由控制器负责通知所有 broker 更新其元数据信息。
- 当使用
kafka-topics.sh
脚本为某个 topic 增加分区数量时,同样还是由控制器负责分区的重新分配。
Controller 选举机制
在 kafka 集群启动的时候,会自动选举一台 broker 作为 controller 来管理整个集群,选举的过程是集群中每个 broker 都会尝试在 zookeeper 上创建一个 /controller
临时节点,zookeeper 会保证有且仅有一个 broker 能创建成功,这个 broker 就会成为集群的总控器 controller。
当这个 controller 角色的 broker 宕机了,此时 zookeeper 临时节点会消失,集群里其他 broker 会一直监听这个临时节点,发现临时节点消失了,就竞争再次创建临时节点,zookeeper 又会保证有一个 broker 成为新的 controller。
Controller 职责
具备控制器身份的 broker 需要比其他普通的 broker 多一份职责,具体细节如下:
- 监听 broker 相关的变化。为 Zookeeper 中的
/brokers/ids/
节点添加BrokerChangeListener
,用来处理 broker 增减的变化。 - 监听 topic 相关的变化。为 Zookeeper 中的
/brokers/topics
节点添加TopicChangeListener
,用来处理 topic 增减的变化;为 Zookeeper 中的/admin/delete_topics
节点添加TopicDeletionListener
,用来处理删除 topic 的动作。 - 从 Zookeeper 中读取获取当前所有与 topic、partition 以及 broker 有关的信息并进行相应的管理。对于所有 topic 所对应的 Zookeeper 中的
/brokers/topics/[topic]
节点添加PartitionModificationsListener
,用来监听 topic 中的分区分配变化。 - 更新集群的元数据信息,同步到其他普通的 broker 节点中。
Partition Replicates 副本选举机制
controller 感知到分区 leader 所在的 broker 挂了 (controller 监听了很多 zk 节点可以感知到 broker 存活),controller 会从每个 parititon 的 replicas 副本列表中取出第一个 broker 作为 leader,当然这个 broker 需要也同时在 ISR 列表里。
Consumer Rebalance 机制
消费者消费消息的 offset 记录机制
每个 consumer 会定期将自己消费分区的 offset 提交给 kafka 内部 topic:__consumer_offsets
,提交过去的时候,key 是 consumerGroupId+topic+分区号
,value 就是当前 offset 的值,kafka 会定期清理 topic 里的消息,最后就保留最新的那条数据,因为__consumer_offsets
可能会接收高并发的请求,kafka 默认给其分配 50 个分区 (可以通过 offsets.topic.num.partitions
设置),这样可以通过加机器的方式抗大并发。
消费者 Rebalance 机制
消费者 rebalance 发生在如果 consumer group 中某个消费者挂了,此时会自动把分配给他的分区交给其他的消费者,如果他又重启了,那么又会把一些分区重新交还给他如下情况可能会触发消费者 rebalance,常见的情况如下:
- consumer 所在服务重启或宕机了
- 动态给 topic 增加了分区
- 消费组订阅了更多的 topic
Rebalance 过程
当有消费者加入消费组时,消费者、消费组及组协调器之间会经历以下几个阶段。
第一阶段:选择组协调器
组协调器 GroupCoordinator:每个 consumer group
都会选择一个 broker 作为自己的组协调器 coordinator,负责监控这个消费组里的所有消费者的心跳,以及判断是否宕机,然后开启消费者 rebalance。consumer group
中的每个 consumer 启动时会向 kafka 集群中的某个节点发送 FindCoordinatorRequest
请求来查找对应的组协调器 GroupCoordinator
,并跟其建立网络连接。
组协调器选择方式:通过如下公式可以选出 consumer 消费的 offset 要提交到__consumer_offsets
的哪个分区,这个分区 leader 对应的 broker 就是这个 consumer group
的 coordinator
公式:hash(consumer group id) % __consumer_offsets主题的分区数
第二阶段:加入消费组 JOIN GROUP
在成功找到消费组所对应的 GroupCoordinator
之后就进入加入消费组的阶段,在此阶段的消费者会向 GroupCoordinator
发送 JoinGroupRequest
请求,并处理响应。然后 GroupCoordinator
从一个 consumer group
中选择第一个加入 group 的 consumer 作为 **leader (消费组协调器)**,把 consumer group
情况发送给这个 leader,接着这个 leader 会负责制定分区方案(由于 rebalance 等策略有客户端配置决定,因此分区方案需要 consumer 来制定,以消费组协调器的配置为准)。
第三阶段:SYNC GROUP
consumer leader
通过给 GroupCoordinator
发送 SyncGroupRequest
,接着 GroupCoordinator
就把分区方案下发给各个 consumer,他们会根据指定分区的 leader broker 进行网络连接以及消息消费。
消费者 Rebalance 分区分配策略
主要有三种 rebalance 的策略:range
、round-robin
、sticky
。 Kafka 提供了消费者客户端参数 partition.assignment.strategy
来设置消费者与订阅主题之间的分区分配策略。
默认情况为 range 分配策略,假设一个主题有 10 个分区 (0-9),现在有三个 consumer 消费:
- range 策略:按照分区序号排序,假设
n=分区数/消费者数量=3
,m=分区数%消费者数量 = 1
,那么前m
个消费者每个分配n+1
个分区,后面的(消费者数量-m
)个消费者每个分配n
个分区。比如分区 0-3 给一个 consumer,分区 4-6 给一个 consumer,分区 7-9 给一个 consumer。 - round-robin 策略:轮询分配,比如分区 0、3、6、9 给一个 consumer,分区 1、4、7 给一个 consumer,分区 2、5、8 给一个 consumer
- sticky 策略:在 rebalance 的时候,需要保证如下两个原则。
- 分区的分配要尽可能均匀。
- 分区的分配尽可能与上次分配的保持相同。
sticky 策略当两者发生冲突时,第一个目标优先于第二个目标。
这样可以最大程度维持原来的分区分配的策略。比如对于第一种 range 情况的分配,如果第三个 consumer 挂了,那么重新用 sticky 策略分配的结果如下:
- consumer1 除了原有的 0~3,会再分配一个 7
- consumer2 除了原有的 4~6,会再分配 8 和 9