Kafka的是个复杂的系统,除了基本的Producer,Consumer,Broker外,为了实现完备的功能,Kafka中有许多重要的模块,本文梳理一下这些模块的划分,与他们负责的功能。
运行组件
基本上来说,正常的一个运行Kafka的业务,都需要4个组件:
- Broker。也就是Kafka服务器。
- Producer。也就是消息的生产者,负责把消息传入Broker。
- Consumer。消息的消费者,负责从Broker拉取消息。
- Zookeeper。Broker的运行需要Zookeeper保存一些元数据。
这四大组件的关系如下图所示:
流程也不用我多介绍了。
这里要提的一点就是,Consumer端不再感知Zookeeper了
这个其实是演进出来的,之前的Offset保存的方式导致了Consumer端必须要感知ZK的地址。
但是使用了新的Offset提交方式后,Consumer没有必要感知Zookeeper了,所以在新版本的启动参数中仅仅需要使用--bootstrap-server
指定任意一个Broker地址就行了。
当然这个问题我也去搜集了一下答案:
新版kafka消费者、生产者配置为何使用bootstrap-servers而不是zookeeper服务器地址?
答案中提到了一个Kafka的提案,就是要取代Zookeeper。也是值得看一看的。
控制器
控制器,也叫Kafka Controller。
我们知道Kafka集群中,会有多个Broker,这些Broker并不是对等的关系,和Raft协议一样,其中一个Broker会被选举为Leader,也就是控制器,KafkaController。
为什么需要一个Leader呢?这个在我看来其实有两个原因。
- Zookeeper的性能有限。
- 避免复杂的逻辑。
在早期的Kafka版本中,其实没有控制器这个概念,所有的Broker都是对等的,很多复杂的逻辑难以解决,以及对ZK会造成很大的负载,笔者列举几个:
- 分区的ISR集合变更。每个分区的ISR集合,属于元数据,需要保存到每个Broker上的。分区的Leader副本所在的Broker会首先感知到该分区的ISR变更,它会把这个事件发布上ZK上,然后其他的Broker会监听到这个事件,更新自己的元数据。
- Leader副本出现问题。当一个分区的Leader副本出现问题时,需要重新选举出新的Leader副本,这个事件也是通过注册ZK的监听器实现的。
- Topic的分区分配,分区迁移,优先副本的选举:这是为了负载均衡的分布在不同的Broker上,如果没有Leader,随机的让这些决策由任意一个Broker去完成,会比较复杂。
所有加入KafkaController后,这个被选为Leader的Broker需要做很多事:
- 注册ZK的监听器,事件触发后,将信息传递给其他的Broker。
- 对集群的配置进行决策和任务发放
再具体一点:
- 分区Leader副本出现故障,选举出新的Leader副本
- ISR集合变更,通知给其他的Broker
- Topic的新增,删除,分区分配,分区迁移,副本管理
- 监听其他的Broker的变化,新增,删除等。
消费者协调器,组协调器
消费者协调器(ConsumerCoordinator),组协调器(GroupCoordinator)是为了解决旧版本的消费者再均衡问题而诞生的。
首先让我们思考一下消费组需要解决的问题。
通常来说,一个Topic会有多个分区,而每个分区,都会指派给一个Consumer会消费。
如上图所示,这个Topic共有4个partition,有3个Consumer。
Consumer0分配了P0和P1给它,Consumer1和Consumer2分别分配了P2和P3。
这样很美好,但是美好的事情总是不稳定。
如果Consumer0挂了呢?
那么我们需要把P0和P1分配给Consumer1和Consumer2。
如果多了一个Consumer加入,我们需要把P0分配给它。
这些就是消费者再均衡问题。
怎么解决这个问题呢?
旧版的Kafka中同样使用了很多的ZK的监听器去完成,很复杂。
问题有2:
- ZK负载较大。
- ZK本身的脑裂问题,会导致各个消费者拿到的消费组的状态不一致,产生问题。
解决这个问题的关键和Kafka的控制器的思路一致,我们需要引入Leader来完成重分配。
于是有了组协调器
组协调器(GroupCoordinator)
组协调器:Kafka将全部的消费组分成了多个子集,每个消费组的子集在服务端对应一个GroupCoordinator对其进行管理
组协调器是在服务端的,由某一个Broker担任。
有了组协调器后,某消费组中的所有消费者定时的向其发送心跳包,这样组协调器就能感知该消费组的消费者的个数变更,从而触发分区重分配。
好像解决重分配的问题,只要有了组协调器就行了?
是的,确实是的。
那么消费者协调器是干什么用的?
别急,等我慢慢道来。
消费者协调器
说到这个其实不能不提一个概念,分区分配规则。
X个分区,Y个消费者,怎么分配分区给消费者呢?
当然我们可以轮询着来,但是作为一个完备的框架,这一层分配策略是需要抽象出来的,甚至可以由用户自定义的。
Kafka提供了消费者参数partition.assignment.strategy
来进行配置,可选值如下:
- RangeAssignor:按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,也是默认的分配策略。
- RoundRobinAssignor:轮询分配
- StickyAssignor:前面两种分配方式,都没有考虑分区和Consumer的状态,消费情况,以及之前的分配情况,这种分配结合前面两种状态来决定分配方式。
当然也可以进行自定义分配方式,需要我们在Consumer代码里进行编写。
所以问题来了,为什么配置是在Consumer端?
你可能想到了,灵活配置!。
写到这儿,消费者协调器的存在就可以理解了,真正的分配其实并不是组协调器进行的,而是组协调器会在所有的Consumer中指定一个Leader,这个Leader就叫消费者协调器,真正的分配结果由这个Consumer来执行,消费者协调器把分配结果告诉组协调器,组协调器再通知给所有的消费者结果。