TIPS1:
在Kafka早期版本,对于分区和副本的状态的管理依赖于zookeeper的Watcher和队列:每一个broker都会在zookeeper注册Watcher,所以zookeeper就会出现大量的Watcher, 如果宕机的broker上的partition很多比较多,会造成多个Watcher触发,造成集群内大规模调整;每一个replica都要去再次zookeeper上注册监视器,当集群规模很大的时候,zookeeper负担很重。这种设计很容易出现脑裂和羊群效应以及zookeeper集群过载。
新版本该变了这种设计,使用KafkaController,只有KafkaController
Leader会向zookeeper上注册Watcher,其他broker几乎不用监听zookeeper的状态变化。
Kafka集群中多个broker,有一个会被选举为controller leader,负责管理整个集群中分区和副本的状态,比如partition的leader 副本故障,由controller 负责为该partition重新选举新的leader 副本;当检测到ISR列表发生变化,有controller通知集群中所有broker更新其MetadataCache信息;或者增加某个topic分区的时候也会由controller管理分区的重新分配工作
当broker启动的时候,都会创建KafkaController对象,但是集群中只能有一个leader对外提供服务,这些每个节点上的KafkaController会在指定的zookeeper路径下创建临时节点,只有第一个成功创建的节点的KafkaController才可以成为leader,其余的都是follower。当leader故障后,所有的follower会收到通知,再次竞争在该路径下创建节点从而选举新的leader
KafkaController vs ReplicaManager
KafkaController: 负责管理整个集群中分区和副本的状态
ReplicaManager:负责管理当前broker所有分区和副本的信息,会处理KafkaController发起的一些请求,副本状态的切换,添加/读取消息等
一 Kafka在zookeeper注册的一些节点路径
/brokers/ids/[id] 记录集群中的broker id
/brokers/topics/[topic]/partitions 记录了topic所有分区分配信息以及AR集合
/brokers/topics/[topic]/partitions/[partition_id]/state记录了某partition的leader副本所在brokerId,leader_epoch, ISR集合,zk 版本信息
/controller_epoch 记录了当前Controller Leader的年代信息
/controller 记录了当前Controller Leader的id,也用于Controller Leader的选择
/admin/reassign_partitions 记录了需要进行副本重新分配的分区
/admin/preferred_replica_election:记录了需要进行"优先副本"选举的分区,优先副本在创建分区的时候第一个副本
/admin/delete_topics 记录删除的topic
/isr_change_notification 记录一段时间内ISR列表变化的分区信息
/config 记录的一些配置信息
二 KafkaController核心组件和组件之间的依赖关系
ZookeeperLeaderElector: 主要用于KafkController Leader选举
ControllerContext: 维护了controller需要用到的上下文,同时也缓存一些zookeeper数据,包括可用的broker,全部的topic,分区和副本信息
ControllerChannelManager: 维护Controller Leader与集群中其他broker之间连接,是管理这个集群的基础
TopicDeletionManager: 用于删除指定的topic
PartitionStateMachine: 用于管理集群所有partition状态的状态机
ReplicaStateMachine: 用于管理集群中所有副本状态的状态机
ControllerBrokerRequestBatch: 实现了向broker批量发送请求的功能
PartitionLeaderSelector:选举leader副本的选举策略
IzkChildListener:是zookeeper上的监听器,实现了对zookeeper上某些节点数据,子节点或者session状态的监听,被处罚后调用对应的业务逻辑
组件之间的依赖关系图:
TIPS 2:
TIPS3:
在正常情况下,Kafka中的每个Topic都会有很多个分区,每个分区又会存在多个副本。在这些副本中,存在一个leader分区,而剩下的分区叫做 follower,所有对分区的读写操作都是对leader分区进行的。所以当我们向Kafka写消息或者从Kafka读取消息的时候,必须先找到对应分区的Leader及其所在的Broker地址,这样才可以进行后续的操作。本文将要介绍的就是 Kafka 是如何找到 leader 分区的。
我们知道, Kafka 是使用 Scala 语言编写的,但是其支持很多语言的客户端,包括:C/C++、PHP、Go以及Ruby等等(参见https://cwiki.apache.org/confluence/display/KAFKA/Clients)。这是为什么呢?这是因为 Kafka 内部实现了一套基于TCP层的协议,只要使用这种协议与Kafka进行通信,就可以使用很多语言来操作Kafka。
目前 Kafka 内部支持多达30多种协议,本文介绍的 Kafka 客户端是如何找到 leader 分区就涉及到 Kafka 内部的 Metadata 协议。Metadata 协议主要解决以下四种问题:
-
Kafka中存在哪些主题?
-
每个主题有几个分区?
-
Leader分区所在的broker地址及端口?
-
每个broker的地址及端口是多少?
客户端只需要构造相应的请求,并发送到Broker端,即可获取到上面四个问题的答案。整个过程如下:
-
客户端构造相应的请求
-
客户端将请求发送到Broker端
-
Broker端接收到请求处理,并将结果发送到客户端。
Metadata 请求协议(v0-v3版本)如下:
目前 Metadata 请求协议存在五个版本,v0-v3版本格式一致。但是这些协议存在一个问题:当 Kafka 服务器端将 auto.create.topics.enable参数设置为 ture 时,如果我们查询的主题不存在,Kafka 将会自动创建这个主题,这很可能不是我们想要的结果。所以,基于这个问题,到了 Metadata 请求协议第五版,格式已经变化了,如下:客户端只需要构造一个 TopicMetadataRequest,里面包括我们需要查询主题的名字(TopicNames);当然,我们可以一次查询多个主题,只需要将这些主题放进List里面即可。同时,我们还可以不传入任何主题的名字,这时候 Kafka 将会把内部所有的主题相关的信息发送给客户端。
Kafka 的 Broker 收到客户端的请求处理完之后,会构造一个 TopicMetadataResponse,并发送给客户端。TopicMetadataResponse协议的格式如下:我们可以指定 allow_auto_topic_creation参数来告诉 Kafka 是否需要在主题不存在的时候创建,这时候控制权就在我们了。
可以看到,相应协议里面包含了每个分区的 Leader、Replicas 以及 Isr 信息,同时还包括了Kafka 集群所有Broker的信息。如果处理出现了问题,会出现相应的错误信息码,主要包括下面几个:
而且,Metadata 协议是目前唯一一个可以向任何 Broker 发送的协议。因为任何一个 Broker 在启动之后会存储这些Metadata信息的。而且,Kafka 提供的客户端在获取到 Metadata 信息之后也会将它存储到内存中的。并且在以下几种情况会更新已经缓存下来的 Metadata 信息:
-
在往Kafka发送请求是收到 Not a Leader 异常;
-
在 meta‐data.max.age.ms参数配置的时间过期之后。
以上两种情况 Kafka提供的客户端会自动再发送一次 Metadata 请求,这样就可以获取到更新的信息。整个过程如下:
好了,说了半天的,我们来看看程序里面如何构造 TopicMetadataRequest以及处理 TopicMetadataResponse。
TopicMetadataRequest是通过 SimpleConsumer的 send方法发送的,其返回的是 TopicMetadataResponse,其中就包含了我们需要的信息。 运行上面的程序输出如下:
上面的输出就可以看到各个分区的leader所在机器、isr以及所有replicas等信息。有一点我们需要注意,因为目前存在多个版本的 Metadata 请求协议,我们可以使用低版本的协议与高版本的Kafka集群进行通信,因为高版本的 Kafka 能够支持低版本的 Metadata 请求协议;但是我们不能使用高版本的 Metadata 请求协议与低版本的 Kafka 通信。