在Kafka集群进行工作之前,需要选举出一个Broker来担任Controller角色,负责整体管理集群内的分区和副本状态。选举Controller的过程就是通过抢占Zookeeper的/controller节点来实现的。
当一个集群内的Kafka服务启动时,就会尝试往Zookeeper上创建一个/controller临时节点,并将自己的brokerid写入这个节点。节点的内容如下:
{"version":1,"brokerid":0,"timestamp":"1661492503848"}
Zookeeper会保证在一个集群中,只会有一个broker能够成功创建这个节点。这个注册成功的broker就成了集群当中的Controller节点。
当一个应用在Zookeeper上创建了一个临时节点后,Zookeeper需要这个应用一直保持连接状态。如果Zookeeper长时间检测不到应用的心跳信息,就会删除临时节点。同时,Zookeeper还允许应用监听节点的状态,当应用状态有变化时,会向该节点对应的所有监听器广播节点变化事件。
这样,如果集群中的Controller节点服务宕机了,Zookeeper就会删除/controller节点。而其他未注册成功的Broker节点,就会感知到这一事件,然后开始竞争,再次创建临时节点。这就是Kafka基于Zookeeper的Controller选举机制。
选举产生的Controller节点,就会负责监听Zookeeper中的其他一些关键节点,触发集群的相关管理工作。例如:
另外,Controller还需要负责将元数据推送给其他Broker。
在Kafka中,一个Topic下的所有消息,是分开存储在不同的Partiton中的。在使用kafka-topics.sh脚本创建Topic时,可以通过–partitions 参数指定Topic下包含多少个Partition,还可以通过–replication-factors参数指定每个Partition有几个备份。而在一个Partition的众多备份中,需要选举出一个Leader Partition,负责对接所有的客户端请求,并将消息优先保存,然后再通知其他Follower Partition来同步消息。
在理解Leader Partition选举机制前,需要了解几个基础的概念:
其中,AR和ISR比较关键,可以通过kafka-topics.sh的–describe指令查看。
[oper@worker1 kafka_2.13-3.2.0]$ bin/kafka-topics.sh -bootstrap-server worker1:9092 --describe --topic disTopic
Topic: disTopic TopicId: vX4ohhIER6aDpDZgTy10tQ PartitionCount: 4 ReplicationFactor: 2 Configs: segment.bytes=1073741824
Topic: disTopic Partition: 0 Leader: 2 Replicas: 2,1 Isr: 2,1
Topic: disTopic Partition: 1 Leader: 1 Replicas: 1,0 Isr: 1,0
Topic: disTopic Partition: 2 Leader: 2 Replicas: 0,2 Isr: 2,0
Topic: disTopic Partition: 3 Leader: 2 Replicas: 2,0 Isr: 2,0
这个结果中,AR就是Replicas列中的Broker集合。而这个指令中的所有信息,其实都是被记录在Zookeeper中的。
接下来,Kafka设计了一套非常简单高效的Leader Partition选举机制。在选举Leader Partition时,会按照AR中的排名顺序,靠前的优先选举。只要当前Partition在ISR列表中,也就是是存活的,那么这个节点就会被选举成为Leader Partition。
例如,我们可以设计一个实验来验证一下LeaderPartiton的选举过程。
#1、创建一个有三个Partition的Topic
[oper@worker1 kafka_2.13-3.2.0]$ bin/kafka-topics.sh --bootstrap-server worker1:9092 --create --replication-factor 3 --partitions 4 --topic secondTopic
Created topic secondTopic.
#2、查看Topic的Partition情况 可以注意到,默认的Leader就是ISR的第一个节点。
[oper@worker1 kafka_2.13-3.2.0]$ bin/kafka-topics.sh -bootstrap-server worker1:9092 --describe --topic secondTopic
Topic: secondTopic TopicId: GluwugzmQV26zeqndtbGPA PartitionCount: 4 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: secondTopic Partition: 0 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0
Topic: secondTopic Partition: 1 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: secondTopic Partition: 2 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1
Topic: secondTopic Partition: 3 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1
#3、在worker3上停掉brokerid=2的kafka服务。
[oper@worker3 kafka_2.13-3.2.0]$ bin/kafka-server-stop.sh
#4、再次查看SecondTopic上的Partiton分区情况
[oper@worker1 kafka_2.13-3.2.0]$ bin/kafka-topics.sh -bootstrap-server worker1:9092 --describe --topic secondTopic
Topic: secondTopic TopicId: GluwugzmQV26zeqndtbGPA PartitionCount: 4 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: secondTopic Partition: 0 Leader: 1 Replicas: 2,1,0 Isr: 1,0
Topic: secondTopic Partition: 1 Leader: 1 Replicas: 1,0,2 Isr: 1,0
Topic: secondTopic Partition: 2 Leader: 0 Replicas: 0,2,1 Isr: 0,1
Topic: secondTopic Partition: 3 Leader: 0 Replicas: 2,0,1 Isr: 0,1
从实验中可以看到,当BrokerId=2的kafka服务停止后,2号BrokerId就从所有Partiton的ISR列表中剔除了。然后,Partiton0和Partition3的Leader节点原本是Broker2,当Broker2的Kafka服务停止后,都重新进行了Leader选举。Parition0预先评估的是Replicas列表中Broker2后面的Broker1,Broker1在ISR列表中,所以他被最终选举成为Leader。Partition3也根据相同的逻辑,选择了Broker0作为Leader。
当Partiton选举完成后,Zookeeper中的信息也被及时更新了。
/brokers/topics/secondTopic: {"partitions":{"0":[2,1,0],"1":[1,0,2],"2":[0,2,1],"3":[2,0,1]},"topic_id":"GluwugzmQV26zeqndtbGPA","adding_replicas":{},"removing_replicas":{},"version":3}
/brokers/topics/secondTopic/partitions/0/state: {"controller_epoch":6,"leader":1,"version":1,"leader_epoch":1,"isr":[1,0]}
Leader Partitoin选举机制能够保证每一个Partition同一时刻有且仅有一个Leader Partition。但是,是不是有Leader Partition就够了呢?