这是[码哥]Kafka 系列文章的第二篇,码哥将从原理、实践和源码角度为大家深入剖析并实践 Kafka。此系列包括[原理篇]、[实践篇]和[源码篇]。这篇是[原理篇]的第二篇,主要讲解 Kafka 的架构和实现原理。
读者可以回顾之前的文章《Kafka 性能篇:为何 Kafka 这么"快"?》。
今天我们来深入讲解 Kafka 的架构和实现原理。[码哥]将从架构和细节入手,以生动的图深入讲解 Kafka 的实现原理。
我想很多同学之前可能已经看过很多 Kafka 原理相关的文章,但往往看时"牛逼"声连连,激情满满,总觉得自己又学习到了各种“吊炸天”的技术。但很多同学往往是不觉明厉,把文章结合面试题背一背还能应付一下半吊子面试官。可以遇到老司机面试官,或是进入实战,却对很多概念和实现摸棱两可。
所以,[码哥]决定图解 Kakfa,却让很多半懂不懂的同学可以加深对 Kafka 实现原理的理解。
同时建议读者同学结合 Kafka 的配置去了解 Kafka 的实现原理,Kafka 有大量的配置,这也是 Kafka 高度扩展的一个表现,很多同学对 Kafka 的配置也不敢轻易改动。所以理解这些配置背后的实现原理,可以让我们在实践中懂得如何使用和优化 Kafka。既可面试造火箭,也可以实战造火箭。
Kafka 配置说明链接:https://kafka.apache.org/documentation
下面是本文的主要的内容:
![](https://i-blog.csdnimg.cn/blog_migrate/ffa773c1a427cbb949e4fa6813b54175.png)
由于内容太多,怕步子迈太大扯着蛋,[码哥]决定将文章分成三篇。此文只会涉及上面图中"橙色"的部分。
从本文你将学习到:
Kafka 架构设计哲学和原理
Kafka 中 zookeeper 的作用
Kafka Controller 实现原理
Kafka Network 原理
开篇寄语
尽可能做一些产品出来,有一个作品很重要,这是别人了解你的窗口。如果可能,给自己开一个公众号或者一个博客,记录自己每天的见闻思考。刚开始记会很凌乱没有逻辑,但坚持下去一定会有很大价值。
Architecture
理解 Kafka 架构,就是理解 Kafka 的各种组件的概念,以及这些组件的关系。先简单看一下各组件及其简单说明。
不要去尝试记忆他们
Producer: 生产者,发送消息的一方。生产者负责创建消息,然后将其发送到 Kafka。
Consumer: 消费者,接受消息的一方。消费者连接到 Kafka 上并接收消息,进而进行相应的业务逻辑处理。
Consumer Group: 一个消费者组可以包含一个或多个消费者。使用多分区 + 多消费者方式可以极大提高数据下游的处理速度,同一消费组中的消费者不会重复消费消息,同样的,不同消费组中的消费者消息消息时互不影响。Kafka 就是通过消费组的方式来实现消息 P2P 模式和广播模式。
Broker: 服务代理节点。Broker 是 Kafka 的服务节点,即 Kafka 的服务器。
Topic: Kafka 中的消息以 Topic 为单位进行划分,生产者将消息发送到特定的 Topic,而消费者负责订阅 Topic 的消息并进行消费。
Partition: Topic 是一个逻辑的概念,它可以细分为多个分区,每个分区只属于单个主题。同一个主题下不同分区包含的消息是不同的,分区在存储层面可以看作一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。
Offset: offset 是消息在分区中的唯一标识,Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,Kafka 保证的是分区有序性而不是主题有序性。
Replication: 副本,是 Kafka 保证数据高可用的方式,Kafka 同一 Partition 的数据可以在多 Broker 上存在多个副本,通常只有主副本对外提供读写服务,当主副本所在 broker 崩溃或发生网络异常,Kafka 会在 Controller 的管理下会重新选择新的 Leader 副本对外提供读写服务。
Record: 实际写入 Kafka 中并可以被读取的消息记录。每个 record 包含了 key、value 和 timestamp。
我们理解了也就自然记住了
我们应该通过理解的方式去记忆它们。
生产者-消费者
生产者
-消费者
是一种设计模式,生产者
和消费者
之间通过添加一个中间组件
来达到解耦。生产者
向中间组件
生成数据,消费者
消费数据。
就像 65 哥读书时给小芳写情书,这里 65 哥就是生产者
,情书就是消息
,小芳就是消费者
。但有时候小芳不在,或者比较忙,65 哥也比较害羞,不敢直接将情书塞小芳手里,于是将情书塞在小芳抽屉中。所以抽屉就是这个中间组件
。
![](https://i-blog.csdnimg.cn/blog_migrate/d2369044d648e935748645515c073340.png)
在程序中我们通常使用Queue
来作为这个中间组件
。可以使用多线程向队列中写入数据,另外的消费者线程依次读取队列中的数据进行消费。模型如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/134ced2cf32932143a14173f6dbd9473.png)
生产者
-消费者
模式通过添加一个中间层,不仅可以解耦生产者和消费者,使其易于扩展,还可以异步化调用、缓冲消息等。
分布式队列
后来 65 哥和小芳异地了,65 哥在卷都
奋斗,小芳在魔都
逛街。于是只能通过邮局
寄暧昧信了。这样 65 哥、邮局和小芳就成了分布式
的了。65 哥将信件发给邮局,小芳从邮局拿到 65 哥写的信,再回去慢慢看。
![](https://i-blog.csdnimg.cn/blog_migrate/d25db99f1cbce2c42b25925a96b7d0de.png)
Kafka 的消息生产者
就是Producer
,上游消费者进程添加 Kafka Client 创建 Kafka Producer,向 Broker 发送消息,Broker 是集群部署在远程服务器上的 Kafka Server 进程,下游消费者进程引入 Kafka Consumer API 持续消费队列中消息。
因为 Kafka Consumer 使用 Poll 的模式,需要 Consumer 主动拉去消息。所有小芳只能定期去邮局拿信件了(呃,果然主动权都在小芳手上啊)。
![](https://i-blog.csdnimg.cn/blog_migrate/b6814e596dd6c7611f861ffb87966b13.png)
主题
邮局不能只为 65 哥服务,虽然 65 哥一天写好几封信。但也无法挽回邮局的损失。所以邮局是可以供任何人寄信。只需要寄信人写好地址(主题),邮局建有两地的通道就可以发收信件了。
Kafka 的 Topic 才相当于一个队列,Broker 是所有队列部署的机器。可以按业务创建不同的 Topic,Producer 向所属业务的 Topic 发送消息,相应的 Consumer 可以消费并处理消息。
![](https://i-blog.csdnimg.cn/blog_migrate/5108d745b21faf3cb9dc91f85044b5eb.png)
分区
由于 65 哥写的信太多,一个邮局已经无法满足 65 哥的需求,邮政公司只能多建几个邮局了,65 哥将信件按私密度分类(分区策略),从不同的邮局寄送。
![](https://i-blog.csdnimg.cn/blog_migrate/a44eb98c795eaae435ff1e6a493fcbbc.png)
同一个 Topic 可以创建多个分区。理论上分区越多并发度越高,Kafka 会根据分区策略将分区尽可能均衡的分布在不同的 Broker 节点上,以避免消息倾斜,不同的 Broker 负载差异太大。分区也不是越多越好哦,毕竟太多邮政公司也管理不过来。具体的原因可以参考[码哥]之前的文章《Kafka 性能篇:为何 Kafka 这么"快"?》
副本
为防止由于邮局的问题,比如交通断啦,邮车没油啦。导致 65 哥的暧昧信无法寄到小芳手上,使得 65 哥晚上远程跪键盘。邮局决定将 65 哥的信件复制几份发到多个正常的邮局,这样只要有一个邮局还在,小芳就可以收到 65 哥的信了。
Kafka 采用分区副本的方式来保证数据的高可用,每个分区都将建立指定数量的副本数,kakfa 保证同一分区副本尽量分布在不同的 Broker 节点上,以防止 Broker 宕机导致所有副本不可用。Kafka 会为分区的多个副本选举一个作为主副本(Leader),主副本对外提供读写服务,从副本(Follower)实时同步 Leader 的数据。
![](https://i-blog.csdnimg.cn/blog_migrate/5fca95f331ef50ffdbd45639e6de80e6.png)
多消费者
哎,65 哥的信件满天飞,小芳天天跑邮局,还要一一拆开看,65 哥写的信又臭又长,让小芳忙得满身大汉大汗。于是小芳啪的一下,很快啊,变出多个分身去不同的邮局取信,这样小芳终于可以挤出额外的时间逛街了。
广播消息
邮局最近提供了定制明信片业务,每个人都可以设计明信片,同一个身份只能领取一种明信片。65 哥设计了一堆,广播给所有漂亮的小妹妹都可以来领取,美女啪变出的分身也可以来领取,但是同一个身份的多个分身只能取一种明信片。
Kafka 通过 Consumer Group 来实现广播模式消息订阅,即不同 group 下的 consumer 可以重复消费消息,相互不影响,同一个 group 下的 consumer 构成一个整体。
最后我们完成了 Kafka 的整体架构,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/6f63faced0c765c3d329e7f42c167c9c.png)
Zookeeper
Zookeeper 是一个成熟的分布式协调服务,它可以为分布式服务提供分布式配置服、同步服务和命名注册等能力.。对于任何分布式系统,都需要一种协调任务的方法。Kafka 是使用 ZooKeeper 而构建的分布式系统。但是也有一些其他技术(例如 Elasticsearch 和 MongoDB)具有其自己的内置任务协调机制。
Kafka 将 Broker、Topic 和 Partition 的元数据信息存储在 Zookeeper 上。通过在 Zookeeper 上建立相应的数据节点,并监听节点的变化,Kafka 使用 Zookeeper 完成以下功能:
Kafka Controller 的 Leader 选举
Kafka 集群成员管理
Topic 配置管理
分区副本管理
我们看一看 Zookeeper 下 Kafka 创建的节点,即可一目了然的看出这些相关的功能。
![](https://i-blog.csdnimg.cn/blog_migrate/78853004bc1cf121ba2e25ef5722cbab.png)
Controller
Controller 是从 Broker 中选举出来的,负责分区 Leader 和 Follower 的管理。当某个分区的 leader 副本发生故障时,由 Controller 负责为该分区选举新的 leader 副本。当检测到某个分区的 ISR(In-Sync Replica)集合发生变化时,由控制器负责通知所有 broker 更新其元数据信息。当使用kafka-topics.sh
脚本为某个 topic 增加分区数量时,同样还是由控制器负责分区的重新分配。
Kafka 中 Contorller 的选举的工作依赖于 Zookeeper,成功竞选为控制器的 broker 会在 Zookeeper 中创建/controller
这个临时(EPHEMERAL)节点。
选举过程
![](https://i-blog.csdnimg.cn/blog_migrate/b57917b2c3dcec7fc5487dc7f33215c3.png)
Broker 启动的时候尝试去读取/controller
节点的brokerid
的值,如果brokerid
的值不等于-1,则表明已经有其他的 Broker 成功成为 Controller 节点,当前 Broker 主动放弃竞选;如果不存在/controller
节点,或者 brokerid 数值异常,当前 Broker 尝试去创建/controller
这个节点,此时也有可能其他 broker 同时去尝试创建这个节点,只有创建成功的那个 broker 才会成为控制器,而创建失败的 broker 则表示竞选失败。每个 broker 都会在内存中保存当前控制器的 brokerid 值,这个值可以标识为 activeControllerId。
实现
![](https://i-blog.csdnimg.cn/blog_migrate/82a5ea1631207d301d4a173b16d25619.png)
Controller 读取 Zookeeper 中的节点数据,初始化上下文(Controller Context),并管理节点变化,变更上下文,同时也需要将这些变更信息同步到其他普通的 broker 节点中。Controller 通过定时任务,或者监听器模式获取 zookeeper 信息,事件监听会更新更新上下文信息,如图所示,Controller 内部也采用生产者-消费者实现模式,Controller 将 zookeeper 的变动通过事件的方式发送给事件队列,队列就是一个LinkedBlockingQueue
,事件消费者线程组通过消费消费事件,将相应的事件同步到各 Broker 节点。这种队列 FIFO 的模式保证了消息的有序性。
职责
Controller 被选举出来,作为整个 Broker 集群的管理者,管理所有的集群信息和元数据信息。它的职责包括下面几部分:
处理 Broker 节点的上线和下线,包括自然下线、宕机和网络不可达导致的集群变动,Controller 需要及时更新集群元数据,并将集群变化通知到所有的 Broker 集群节点;
创建 Topic 或者 Topic 扩容分区,Controller 需要负责分区副本的分配工作,并主导 Topic 分区副本的 Leader 选举。
管理集群中所有的副本和分区的状态机,监听状态机变化事件,并作出相应的处理。Kafka 分区和副本数据采用状态机的方式管理,分区和副本的变化都在状态机内会引起状态机状态的变更,从而触发相应的变化事件。
“65 哥:状态机啊,听起来好复杂。
”
Controller 管理着集群中所有副本和分区的状态机。大家不要被状态机
这个词唬住了。理解状态机很简单。先理解模型,即这是什么关于什么模型,然后就是模型的状态有哪些,模型状态之间如何转换,转换时发送相应的变化事件。
Kafka 的分区和副本状态机很简单。我们先理解,这分别是管理 Kafka Topic 的分区和副本的。它们的状态也很简单,就是 CRUD,具体说来如下:
分区状态机
PartitionStateChange,管理 Topic 的分区,它有以下 4 种状态:
NonExistentPartition:该状态表示分区没有被创建过或创建后被删除了。
NewPartition:分区刚创建后,处于这个状态。此状态下分区已经分配了副本,但是还没有选举 leader,也没有 ISR 列表。
OnlinePartition:一旦这个分区的 leader 被选举出来,将处于这个状态。
OfflinePartition:当分区的 leader 宕机,转移到这个状态。
我们用一张图来直观的看看这些状态是如何变化的,以及在状态发生变化时 Controller 都有哪些操作:
![](https://i-blog.csdnimg.cn/blog_migrate/10957c08f746607499474d54927fa2cf.png)
副本状态机
ReplicaStateChange,副本状态,管理分区副本信息,它也有 4 种状态:
NewReplica: 创建 topic 和分区分配后创建 replicas,此时,replica 只能获取到成为 follower 状态变化请求。
OnlineReplica: 当 replica 成为 parition 的 assingned replicas 时,其状态变为 OnlineReplica, 即一个有效的 OnlineReplica。
OfflineReplica: 当一个 replica 下线,进入此状态,这一般发生在 broker 宕机的情况下;
NonExistentReplica: Replica 成功删除后,replica 进入 NonExistentReplica 状态。
副本状态间的变化如下图所示,Controller 在状态变化时会做出相应的操作:
![](https://i-blog.csdnimg.cn/blog_migrate/5061f06a8809fd57dbd5b3fccdf65d35.png)
Network
Kafka 的网络通信模型是基于 NIO 的 Reactor 多线程模型来设计的。其中包含了一个Acceptor
线程,用于处理新的连接,Acceptor
有 N 个 Processor
线程 select 和 read socket 请求,N 个 Handler
线程处理请求并相应,即处理业务逻辑。下面就是 KafkaServer 的模型图:
![](https://i-blog.csdnimg.cn/blog_migrate/e068c4bf8235b2fa2f998e4bbe34d5ba.png)
之后的 Kafka 源码篇,[码哥]将从源码的角度来讲解这些原理在代码上的具体实现,各位敬请期待啊。
往期回顾
文章如有错误,感谢指正,关注我,获取真正的硬核知识点。另外技术读者群也开通了,后台回复「加群」获取「码哥字节」作者微信,一起成长交流。
以上就是本篇所有内容觉得不错请点赞、分享,「码哥字节」感激不尽。