最近confluent社区发了一篇文章,主要是关于Kafka未来2.8版本将放弃Zookeeper,这是对Kafka用户的一个重要改进。在部署Kafka之前,必须先部署Zookeeper,之后只需要单独部署Kafka即可。[1]1.Kafka简介ApacheKafka最早由Linkedin开发,后来捐赠给ApackFoundation。Kafka官方定义为分布式流处理平台,因其高吞吐量、持久化、横向扩展等特点而被广泛应用。目前Kafka有以下具体功能:消息队列,Kafka具有系统解耦、流量调峰、缓冲、异步通信等消息队列功能。Kafka作为一个分布式存储系统,可以持久化消息,使用多副本实现故障转移,可以作为数据存储系统。对于实时数据处理,Kafka提供了一些与数据处理相关的组件,如KafkaStreams、KafkaConnect等具有实时数据处理功能。下图是Kafka的消息模型:[2]通过上图,介绍Kafka中的几个主要概念:生产者和消费者:消息队列中的生产者和消费者,生产者向队列中推送消息,消费者从队列。consumergroup:消费者的集合,可以并行消费同一主题下不同分区的消息。broker:Kafka集群中的服务器。topic:消息的分类。partition:主题物理分组,一个主题可以有分区,每个分区中的消息会被分配一个有序的id作为偏移量。每个消费者组只能有一个消费者消费一个分区。2、Kafka和Zookeeper的关系Kafka的架构如下图所示:从图中可以看出,Kafka的工作需要Zookeeper的配合。那么他们是如何协同工作的呢?看下图:从图中可以看出,Kafka的工作需要Zookeeper的配合。那么他们是如何协同工作的呢?看下图:2.1注册中心2.1.1Broker注册从上图可以看出,Broker的分布式部署需要一个注册中心进行统一管理。Zookeeper使用一个专门的节点来保存Broker服务列表,即/brokers/ids。broker启动时向Zookeeper发送注册请求,Zookeeper在/brokers/ids下创建broker节点,如/brokers/ids/[0...N],并保存broker的IP地址和端口。这个节点是一个临时节点。一旦broker宕机,这个临时节点将被自动删除。2.1.2主题注册Zookeeper也会为主题分配一个单独的节点,每个主题都会以/brokers/topics/[topic_name]的形式记录在Zookeeper中。一个topic消息会被保存到多个分区中,这些分区和broker的对应关系也需要保存到Zookeeper中。分区保存多个副本,上图中红色分区为leader副本。当leader副本所在的broker发生故障时,partition需要重新选举leader,这需要Zookeeper来完成。Broker启动后,会将自己的BrokerID注册到对应主题节点的分区列表中。我们查看信息,一个topic是xxx,分区号是1,命令如下:[root@master]get/brokers/topics/xxx/partitions/1/state{"controller_epoch":15,"leader":11,"version":1,"leader_epoch":2,"isr":[11,12,13]}当broker退出时,Zookeeper会更新topic对应的分区列表。2.1.3消费者注册消费者组也会向Zookeeper注册,Zookeeper会分配节点保存相关数据。节点路径为/consumers/{group_id},有3个子节点,如下图所示:这样Zookeeper就可以记录分区和消费者的关系,以及分区的偏移量。[3]2.2负载均衡broker向Zookeeper注册后,生产者根据broker节点感知broker服务列表的变化,从而实现动态负载均衡。消费者组中的消费者可以根据主题节点信息从特定分区拉取消息,实现负载均衡。事实上,Kafka在Zookeeper中保存了大量的元数据。见下图:随着broker、topic、partition的增加,存储的数据量也会增加。3.Controller介绍经过上一节的描述,我们看到Kafka非常依赖Zookeeper,没有ZookeeperKafka是无法独立运行的。Kafka是如何与Zookeeper交互的?如下图所示:[4]图中,Kafka集群中会选举出一个broker作为Controller,负责与Zookeeper进行交互。它负责管理整个Kafka集群中所有分区和副本的状态。其他broker监听Controller节点的数据变化。Controller的选举取决于Zookeeper。选举成功后,Zookeeper会创建一个/controller临时节点。Controller的具体职责如下:监控分区变化。例如,当某个partition的leader失效时,Controller会为该partition选举出新的leader。当检测到分区的ISR集发生变化时,Controller会通知所有broker更新元数据。当主题添加分区时,Controller将负责重新分配分区。监控topic相关的变化监控broker相关的变化集群元数据管理下图是Controller、Zookeeper和broker的交互细节:Controller选举成功后,会从Zookeeper集群中拉取一整套元数据来初始化ControllerContext,这些元数据缓存在Controller节点上。当集群发生变化时,比如增加了topic分区,Controller不仅需要改变本地的缓存数据,还需要将改变的信息同步到其他Brokers。Controller监听到Zookeeper事件、定时任务事件等事件后,将这些事件按顺序暂存在LinkedBlockingQueue中,由事件处理线程依次处理。这些进程大多需要和Zookeeper交互,Controller需要更新自己的元数据。.4、Zookeeper带来的问题Kafka本身是一个分布式系统,但是又需要另外一个分布式系统来管理,这无疑增加了复杂度。4.1运维复杂度采用Zookeeper。在部署Kafka时,必须部署两个系统。Kafka运维人员必须具备Zookeeper运维能力。4.2Controller故障处理Kafaka依靠单个Controller节点与Zookeeper进行交互。如果Controller节点出现故障,则需要从broker中选择一个新的Controller。如下图,新的Controller成为broker3。新的Controller选举成功后,会再次从Zookeeper中拉取元数据进行初始化,需要通知所有其他broker更新ActiveControllerId。旧的Controller需要关闭监听器、事件处理线程和定时任务。当分区数非常多时,这个过程非常耗时,Kafka集群在这个过程中无法工作。4.3分区瓶颈当分区数量增加时,Zookeeper保存的元数据更多,Zookeeper集群的压力增加。达到一定程度后,监控延迟增加,影响Kafaka的工作。因此,单个Kafka集群承载的partition数量是一个瓶颈。而这正是一些业务场景所需要的。5、升级前后架构对比如下:KIP-500将之前的Controller换成了QuorumController,Quorum中的每个Controller节点都会保存所有的元数据,通过卡夫协议。这样即使QuorumController节点出现故障,新Controller的迁移也会非常快。据官方介绍,升级后的Kafka可轻松支持百万级分区。Kafak团队采用KafkaRaftMetadata模式,简称KRaft,通过Raft协议同步数据。Kafka的用户基数非常大,需要不停服升级。目前,去除Zookeeper的Kafka代码KIP-500已经提交到trunk分支,计划在未来的2.8版本中发布。Kafaka计划在3.0版本兼容ZookeeperController和QuorumController,方便用户进行灰度测试。[5]6.总结在大规模集群和云原生的背景下,Zookeeper的使用给Kafka的运维和集群性能带来了很大的压力。去掉Zookeeper是必然趋势,这也符合简洁的架构思想。参考[1]参考1:https://www.confluent.io/blog/kafka-without-zookeeper-a-sneak-peek/[2]参考2:https://blog.csdn.net/Zidingyi_367/article/details/110490910[3]参考3:https://www.jianshu.com/p/a036405f989c[4]参考4:https://honeypps.com/mq/kafka-controller-analysis/[5]参考5:https://mp.weixin.qq.com/s/ev6NM6hptltQBuTaCHJCQQ
