从本系列开始,我将对Kafka这一流行的分布式消息中间件进行透彻分析。我之前在MQ系列中,对RocketMQ的核心原理进行过讲解,其实它俩在设计上有很多相似地方。
Kafka 是 Linkedin 采用 Scala 语言开发的 一个 多分区 、 多副本 、 基于 ZooKeeper 的分布式消息系统,现己被捐献给 Apache 基金会 。 目前 Kafka 已经定位为一个分布式流式处理平台,它以高吞吐低延迟、可持久化、可水平扩展、支持流数据处理等多种特性而被广泛使用。
本章,我会从整体上对Kafka的基本架构和一些核心概念做阐述,便于读者初步了解Kafka,后续章节再对细节作详述。
一、核心组件
在Kafka中,一共有四个核心组件: Zookeeper集群 、 Broker 、 Producer 、 Customer ,它们之间的基本关系可以用下面这张图表示:
- Zookeeper集群: 负责Kafka集群的元数据管理、控制器选举等操作;
- Producer: 生产者,也就是发送消息的一方,负责创建消息,然后将其投递到Kafka中;
- Consumer :消费者,也就是接收消息的一方,消费者连接到 Kafka 上并接收消息,进行相应的业务逻辑处理;
- Broker :Broker 可以简单地看作一个独立的 Kafka服务节点或Kafka 服务进程实例,一个或多个 Broker 组成了 一个 Kafka 集群。一般而言,生产环境一台服务器运行一个Broker进程。
## 二、高性能
Kakfa之所以具有高吞吐低延迟的特性,核心有四个点:
- 顺序读写磁盘;
- MMap内存映射技术;
- 零拷贝技术;
- 数据批处理。
2.1 顺序读写磁盘
Kafka在持久化消息的时候,仅仅是将消息追加到日志文件的末尾,也就是 磁盘顺序写 ,性能极高。
2.2 MMap内存映射
MMAP 也就是 内存映射文件 ,在64位操作系统中一般可以表示 20G 的数据文件,它的工作原理是直接利用操作系统的 Page 来实现文件到物理内存的直接映射,完成映射之后对物理内存的操作会被同步到硬盘上。
通过 MMAP 技术,进程可以像读写硬盘一样读写内存(逻辑内存),不必关心内存的大小,因为有虚拟内存兜底。这种方式可以极大的提升I/O能力,省去了数据从用户空间到内核空间复制的开销。
但是这里要注意:写到 MMAP 中的数据并没有真正写到硬盘,操作系统会定时进行刷盘操作,将Page Cache中的数据flush到磁盘。
Kafka提供了
producer.type
参数,来控制是否同步刷盘。
2.3 零拷贝
Consumer在消费消息时,会请求Kafka Broker从磁盘文件里读取消息,然后通过网络发送出去,整个流程涉及到了 零拷贝 ,如下图:
可以看到,Kafka Broker利用了Linux的sendfile
函数,直接把读取操作交给OS,OS会查看Page Cache中是否有数据,如果没有就从磁盘上读取并缓存到Page Cache,如果有就直接将OS Cache里的数据拷贝给网卡引擎,这样就减少了上下文切换和数据复制的开销。
2.4 数据批处理
当Consumer需要消费数据时,首先想到的是消费一条,Kafka发送一条。但实际上,Kafka 会把一批消息压缩存储,当消费者拉取数据时,实际上是拉到一批数据。比如说100万条消息压缩放到一个文件中可能就是10M的数据量,如果消费者和Kafka之间网络良好,10MB大概1秒就能发送完,即Kafka每秒处理了100万条消息。
正是因为这种批处理的方式,Kafka才有了极高的吞吐量。
三、可扩展
3.1 数据分片
Kafka 中的消息以 主题(Topic) 为单位进行归类,生产者发送消息时,必须指定消息的主题,而消费者负责订阅主题并进行消费。
主题(Topic)是一个 逻辑 上的概念,它可以细分为多个分区,同一主题下的不同分区包含的消息是不同的,各个分区构成了一个完整的数据集,这实际上就是 数据分散集群 的模式。
分区(Partition) 在存储层面可以看作一个可追加的日志( Log )文件,消息在被追加到分区日志的时候都会分配一个特定的偏移量( offset )。 offset 是消息在分区中的唯一标识, Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说, Kafka 保证的是分区有序而不是主题有序 。
四、高可用
4.1 多副本冗余
Kafka为了维持集群的高可用,引入了 副本(Replica) 机制,每个分区都可以有多个副本,同一分区的不同副本中保存的消息是相同的。 Kafka会选举出一个Leader副本,负责处理 读写 请求 ,其它Follower副本只负责与Leader进行消息同步。
Kafka可以通过多副本机制实现故障的自动转移,当 Kafka 集群中某个 broker 挂掉时,各个副本重新选举leader,最终保证服务可用。
我通过下面一张图来总结下主题(Topic)、分区(Partition)、副本(Replica)这三个概念。下图中,两个Broker进程组成了一个Kafka集群,然后创建了一个主题,这个主题有3个分区,每个分区有2个副本。Kafka会自动均匀分布同一个分区的不同副本到不同的Broker上:
4.2 故障自动转移
Kafka基于Zookeeper实现了故障节点的发现与自动转移。当一个分区的Leader副本挂掉后,其它Follower副本会选举出新的Leader,并往Zookeeper中注册信息。
五、数据同步
5.1 ISR/OSR
每个分区的follower副本都会从leader同步消息数据,既然是同步,就一定有滞后性。这里引申出Kafka中的三个概念:
- AR ( Assigned Replicas ): 在Kafka中,分区中的所有副本(包含Leader)统称为 AR;
- ISR (On-Sync Replicas ): 所有与 Leader 保持一定程度同步的副本(包括 leader在内)组成 ISR;
- OSR ( Out-of-Sync Replicas): 与Leader 副本同步滞后过多的副本(不包括 leader 副本)组成 OSR 。
通过上面的描述可以看出: AR = ISR + OSR 。
Leader 负责维护和跟踪 ISR 集合中所有 follower副本的滞后状态(Leader会维护每个Follower的LEO,Follower来拉取消息时会带上自己的LEO),当follower副本落后太多或失效时,leader 会把它从 ISR 集合中剔除,转移到OSR。默认情况下, 当 leader 副本发生故障时,只有在 ISR 集合中的副本才有资格被选举为新的 leader。
5.2 HW和LEO
那么,到底怎么样才算同步滞后过多呢?这就又涉及到 HW(High Watermark,高水位) 和 *LEO(Log End Offset)*两个概念了。
- HW(High Watermark,高水位) : 标识了 一个特定的消息偏移量( offset ),消费者只能拉取到这个 offset 之前的消息;
- LEO(Log End Offset) :标识了当前日志文件中的下一条待写入消息的offset。
我通过下面这张图来讲解上述的概念:
上图是某个分区的某个副本中的日志文件,这个日志文件中一共有9条消息,第一条消息的offset=0,最后一条消息的offset=8,offset 为 9 的位置用虚线框表示,代表下一条待写入的消息。
上述日志文件的HW=6,表示Consumer只能拉取到 offset 在 0 至 5 之间的消息,offset为6~8之间的消息对Consumer是不可见的;offset = 9 的位置即为当前日志文件的 LEO,所以LEO的大小相于当前日志中的最后一条消息的offset +1。
ISR 集合中的每个副本都会维护自身的 LEO ,而 ISR 集合中最小的 LEO即为这个分区的HW ,对于消费者而言,只能消费HW之前的消息。
Kafka之所以引入ISR机制,是为了有效地权衡 数据可靠性 和 性能 之间的关系,因为完全同步复制影响性能,完全异步复制又可能因为follower落后太多宕机后导致消息丢失。我后面章节会再对ISR机制进行更详细的分析。
六、总结
本章,我先从整体上对Kafka的核心功能和架构进行了分析,从后续章节开始,我将对Kafka每一个组件进行深入剖析。