kafka是家喻户晓的消息队列,也因“纯粹”而闻名(高性能高吞吐、扩展较少较为简单),此篇文章整理Kafka的基本架构,将按照Kafka的版本迭代分别展示架构的演进(截至版本3.0)。我们在这里暂且只讨论Kafka服务端,对于生产者和消费者的逻辑简单带过。
基本术语
扫盲一下Kafka的部分概念:
Producer。mq生产者通用叫法。作为消息的生产者,在生产完消息后需要将消息投送到指定的目的地(某个topic的某个partition)。Producer可以根据指定选择partition的算法或者是随机方式来选择发布消息到哪个partition;
Consumer。mq生产者通用叫法。消息消费者,向Kafka broker读取消息的客户端;,负责订阅和消费消息。消费者用consumerGroup来标识自己。同一个消费组可以并发地消费多个分区的消息,同一个partition也可以由多个consumerGroup并发消费,每个consumer会保留它读取到某个partition的offset。而consumer 通常是通过zookeeper来保留offset的;
Broker。Kafka服务端节点的叫法,用来存储消息,Kafka集群中的每一个服务器都是一个Broker,消费者将从broker拉取订阅的消息;
Topic。mq主题通用叫法。消息的主题、队列,每一个消息都有它的topic,Kafka通过topic对消息进行归类。Kafka中可以将Topic从物理上划分成一个或多个分区(Partition),每个分区在物理上对应一个文件夹,以”topicName_partitionIndex”的命名方式命名,该dir包含了这个分区的所有消息(.log)和索引文件(.index),这使得Kafka的吞吐率可以水平扩展;
Partation。Kafka的分片形式,在很多mq中也被叫做队列。每个分区都是一个 顺序的、不可变的消息队列, 并且可以持续的添加;分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的。 producer在发布消息的时候,每一条消息被发送到broker中,会根据partition规则选择被存储到哪一个partition(默认采用轮询的方式进行写入数据),如果分区规则设置的合理,那么所有的消息将会被均匀的分布到不同的分区中,这样就实现了负载均衡;
Replica。副本,Kafka消息备份,是可靠性的保障,当不进行额外备份时,此值为1。Kafka对消息进行了冗余备份,每个Partition分区都可以有多个副本,每一个副本中包含的消息是相同的(但不保证同一时刻下完全相同)。每个分区至少有一个副本,当分区只有一个副本的时候,就只有Leader副本,没有 Follower。由于这是个屋里存储的单位,所以不存在副本数 > Broker节点数的情况;
ISR(In-Sync Replicas)。是一系列副本的集合,是数据延迟相比主副本在一定范围内的副本(默认配置是500ms通信延迟)合集。当从副本延迟较高(通常是网络问题)会导致与主副本数据较多的不一致,此时该副本会从ISR集合中暂时剔除,直到延迟追上。在Leader宕机后,会从ISR列表而非所有的Follower列表遴选新的Leader,以避免遗漏过多消息;
Leader。指相对于某个Topic的Partition,数据的主分片所在Broker。所以这个概念是Partition的维度,对于不同的Topic或不同的Partition,Leader不一定相同;
Follower。指相对于某个Topic的Partition,数据的从分片(副本)所在Broker。所以这个概念是Partition的维度,对于不同的Topic或不同的Partition,Follower列表不一定相同。
Controller。Kafka的集群管理者,每个集群只会有且只有一个Broker为Controller。Controller负责选举Leader、上下线Broker、Topic的管理和partition扩容、迁移同步管理等;
基本架构概述(3.0版本前)
在3.0版本前,kafka需要依靠外部组件——Zookeeper(下文直接简称zk)来维护集群
Zookeeper节点
我们先通过zk的主要节点了解zk集群在整个服务端集群的职责:
通信过程
Kafka通信的基本原则:在一套消息交互中,生产者消费者不会直接与Follower交互。
常规情况下,一条消息生产的过程为:
生产者从zk获取 topic-partition 相应的 Leader 列表;(所以说,生产者partition的路由规则是由客户端定义的而非服务端)
生产者直接向 Leader 节点通信发送 message;
Leader 节点会将消息写入 log,同时 Follower 会去拉取log进行同步;在这期间,Leader节点根据ACK规则决定何时返回ACK响应;
消费的过程为:
消费者向 broker 集群提交连接请求,返回 controller 的通信url;(所以说,消费者partition的路由规则是由Controller分配的,消费者之间也无法就此达成共识)
消费者指定要消费的 topic 后, 向 controller 提交消费消息请求;
controller 将消费者分配一个或者多个 partition leader 。并且将对应的partition的offset 发送给消费者;
消费者消费完消息后,会向 broker 提交该消息的offset;
Controller broker和Topic的建立
Kafka集群有且只有一个Controller,除了是普通的broker,它也是整个集群的管理者。它的推举很简单:Kafka启动后,所有broker节点均尝试将自身信息注册到zk的/controller节点,成功注册的节点变成为Controller节点。Controller节点的职责主要有下:
监听zk节点,管控创建、删除主题,增加分区并分配leader分区
集群Broker管理(新增 Broker、Broker 主动关闭、Broker 故障)
Leader选举
分区重分配(rebalance)
我们根据不同场景看一下Controller节点的工作方式。
1. Kafka的主题创建过程
Kafka的Topic创建一般是通过命令行,像是一些partition的分配逻辑也包含在内,例如,我们在Kafka的脚本目录下执行:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 3 --topic my_topic
脚本便会创建一个名为my_topic的Topic,副本为3,partition也为3。通过这个脚本的执行,便能确认分区的副本分配方案,会尽可能的平均分配给现有的broker,同时将分配好的方案写入zk(见上面图中节点/brokers/topics/my_topic/partitions……)。
Controller监听了topic节点,所以能第一时间知道我们创建的Topic信息并进行选举和通知分配。
2. 分区Leader选举
Kafka对Leader的选举采用了很粗暴的方式,无论是宕机还是初次创建Topic还没有Leader,直接选择当前ISR列表的首个作为Leader节点。因此Leader的选举已经不会依赖本身了。值得注意的是,在较早版本的Kafka中,依旧采用类似Controller抢注的方式去决定Leader,但很快,这种消耗Zk性能的设计变为人诟病,并替换为Controller统一选举,避免了普通Broker也要监听Zk从而导致潜在的负载风险。
异架构演进(3.0)
异常和故障处理
Leader故障转移
Kafka是很明显的主从结构,当有Broker宕机时,会将响应节点自动从集群移除,如果该节点同时扮演着Leader的角色,便会选取新的Leader。
当我们讨论宕机时,一般指该节点相对于集群其他节点是“失联”的状态,此处的宕机又分两种情形:节点与其他相关kafka节点断开连接;节点与Zk集群断开连接,我们分别讨论一下:
与其他Broker失去联系:对于它作为Follower的主题分片,很明显地,这个节点会被集群从ISR中移除,如果它是Leader,其ISR列表将只有它自己,直到重新恢复。仅仅是这种故障不会影响集群使用。
与Zk失去联系:集群Brokers启动后会将自身信息注册至Zk的/brokers节点下,作为一个临时节点,一旦会话断开,节点便会消失。
作为监听者的Controller会马上得到Broker节点列表发生变更的消息,如果此节点作为数据Leader,Controller会为此从ISR列表选取新的Leader,生产消费者会心跳查询
元数据,发现更新便会与新的Leader做交互。稳定后,如果该Broker重新加入集群,其上现有的消息都会丢失。但如果此节点是数据Follower,在没与其他机器断连的情况下,他依旧在ISR列表中,不会有任何影响,因为客户端也是直接与Leader交互的。