前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Kafka为什么这么快

Kafka为什么这么快

原创
作者头像
潋湄
修改2025-01-18 09:17:32
修改2025-01-18 09:17:32
2390
举报
文章被收录于专栏:消息队列

在消息队列中,Kafka一直被称为是吞吐量最大的消息队列,那么它究竟为什么能够做到传输效率那么快呢?本文带你一探究竟

Kafka的基础架构

在介绍具体原因前,我们先来了解一下Kafka的基础架构,如下图:

Kafka的基础架构
Kafka的基础架构

如果你对其他某种类型的消费队列架构比较熟悉,那么看到这张图你应该不会觉得陌生,它只不过是吧一些组件重命名了一下,这些具体组件的含义如下:

Topic:逻辑队列,实际开发中会将一个业务中的信息放到一个Topic中进行存储

Cluster(Broker):物理集群,相当于Broker集群,每个集群中包含多个Topic

Producer:生产者,负责将消息发送到Topic

Consumer:消费者,负责从Topic中消费已经发送过来的消息

Partition:通常topic会有多个分片,不同的分片直接消息可以并发来进行处理,相当于RocketMQ中Topic下面的MessageQueue

ZooKeeper:相当于Kafka的注册中心,Kafka集群的服务节点使用信息以及注册中心会注册到ZooKeeper中,可以管理Kafka的集群节点信息

Offset:在图里并没有展现,它主要用于从分区Partition中取数据,在Kafka中,偏移量Offset是针对一个分区Partition来设置的

为了防止数据丢失,Kafka也会为一份Partition数据复制多份拷贝,这些拷贝会分散到多个Broker下面进行存储,来防止某个集群突然失效时造成数据丢失

同时这些同一份数据的拷贝也会区分Leader节点Follower节点,Leader节点主要负责负责读写请求,Follower节点主要负责从主节点同步数据,保持数据的完整性

分区的主从节点集群分配
分区的主从节点集群分配

Producer投递数据

批量发送数据

一般地消息投递消费过程很简单:

一条消息的投递消费流程
一条消息的投递消费流程

这个过程很简单,对于Producer端:Producer向Broker投递消息,如果Broker接收成功会返回ACK,对于Consumer端,Consumer发出请求向Broker取消息,Broker将消息发送给Consumer,Consumer发送成功后会返回ACK,由此可见,单单一条消息的投递与取出就要进行五次网络的请求与调用,在高并发环境下这样的调用肯定很浪费时间,因此Kafka应用了批量发送一次发送多条数据,减少网络请求次数:

Kafka批量发送数据
Kafka批量发送数据

数据压缩

而在进行一次网络请求的过程中,如果网络传输携带了过多的信息数据,也会造成性能的下降,为此Kafka引入了数据压缩来减少网络传输过程中的信息量,从而提高网络调用的性能,常见的压缩算法有Gzip(对存储空间要求高)、Snappy(对实时性要求较好)、Zstd(新型算法,对性能要求高)等

因此,在Producer端,主要应用批量发送数据压缩来提高消息发送效率

Broker处理消息

顺序写文件

接下来到了Broker这里对于消息的处理,我们知道在Broker中存储着多个Topic分别代表不同业务的所需消息,而消息从Producer投递过来后,首先会被存储到日志缓冲区后,一定时间Kafka会通过操作系统的后台进程,将日志缓冲区中的内容刷回到磁盘中:

Broker中消息的组织形式
Broker中消息的组织形式

而由于每个日志文件分处于磁盘不同的磁道上,如果每次写入文件不固定,则读写磁头需要一直旋转进行寻道,因此Broker对于消息的写入方式为顺序写,以此来减少每次写入消息时磁头的旋转次数,从而减少写入时间

计算机磁盘的读写磁头寻道读写数据
计算机磁盘的读写磁头寻道读写数据

索引文件

可是在处理Consumer的消费请求时,Broker难免要读取磁盘的数据将来返回给Consumer,那么Broker又是如何快速从磁盘上读取数据的呢,这时就要提到上面图中的两个索引文件偏移量索引文件时间戳索引文件了:

偏移量索引文件:存储索引值与偏移量之间的映射关系,必须是从小到大进行存储的:

偏移量索引文件图示
偏移量索引文件图示

时间戳索引文件:时间戳实在Kafka的0.11.0版本引入的,它实际上就是多建立了一层关于时间戳与索引之间的映射文件:

时间戳索引文件
时间戳索引文件

通过这两个索引文件的映射,就能够快速根据指定偏移量找到对应位置的文件了,从而满足快速查找

零拷贝技术

要想真正了解零拷贝技术,我们首先来看一般情况下消费进程从磁盘获取文件的流程:

消费者从磁盘获取文件流程
消费者从磁盘获取文件流程

我们可以看到,一般情况下,文件会从磁盘读取到内核空间的Read Buffer后,再次经过内核态到用户态的拷贝到应用空间中,之后再从应用空间拷贝到内核空间,一般来说,从内核态到用户态的切换时很费时的,而零拷贝就是减少了内核态到用户态的两次无用转换,因为文件描述符会存储在内核态中,因此零拷贝的核心思想就是:在从磁盘读取文件后,直接将文件传递给消费者的目标文件描述符,从而减少拷贝次数,提升网络传输效率,从这里我们也能发现,零拷贝并不是真正实现了没有文件的拷贝,它只是实现了零CPU拷贝,即CPU并不参与文件的拷贝工作,而一个典型的实现就是Linux的sendfile

正常情况下,获取并发送文件要经历一次read与一次write调用,发生四次文件拷贝以及四次用户态到内核态的切换,使用了sendfile后,只需要进行两次内核态与用户态的切换,而文件拷贝次数也减少为了两次

sendfile零拷贝流程
sendfile零拷贝流程

而Kafka在进行文件传输时,就会应用零拷贝减少文件拷贝次数,从而提高消息处理效率

mmap内核文件映射

一般情况下,如果我们要操作磁盘上的文件,必须从内核态中获取页表地址来获取最终数据,如果操作系统应用的是多级页表,在计算数据所在页表地址甚至要经历好几次I/O,而mmap的引入就是为了很好地解决这个问题,在Kafka中,用户态映射了磁盘上的文件在内核态的页表地址,通过将文件或者设备的一部分映射到进程的页表缓存中,下次获取数据可以直接从页表缓存中获取数据,从而减少磁盘与内核态之间的I/O次数

其实不光在Kafka中应用了mmap思想,在RocketMQ中也应用了这个方法,具体可以看我的这篇文章,又从源码分析mmap映射的运用:https://cloud.tencent.com/developer/article/2485686

mmap相对于sendfile方法就稍逊一筹了,它会调用一次mmap获取文件与一次write发送文件,在用户态与内核态之间会发生四次用户态与内核态切换,而文件拷贝次数也减少了一次用户态到内核态的拷贝,变为了三次

mmap文件拷贝流程
mmap文件拷贝流程

Consumer选择服务集群获取消息

在Consumer获取消息时,也不是随意选择一个服务集群来获取数据,而是每个服务集群会负责指定部分的数据,而当消费节点增多或者减少时,往往需要进行负载均衡策略来重新分配分片消息

Rebalance重新分配分片节点

一般情况下,每个Consumer服务集群负责的分片消息都是平均分配的,而在Kafka的早先版本,对于服务节点的Partition分配还需要依靠手动分配来完成,而在之后,Kafka引入了自动分配策略来实现Partition的分配,其主要思想是在Broker集群中设置消费协调者节点来分配节点,具体步骤如下:

首先如果有Consumer消费节点的加入,所有Consumer会随机向Broker节点发送寻找协调者请求

Consumer发送寻找协调者Broker节点
Consumer发送寻找协调者Broker节点

之后Broker会将对应的协调Broker集群告诉对应的Consumer节点,之后所有Consumer会继续向新的协调者Broker集群发送分配请求:

向Broker协调者发送请求
向Broker协调者发送请求

之后协调者Broker集群会发送回消费者节点选举出Consumer Leader节点,它会负责进行服务节点与Partition分片信息之间的分配:

Broker协调者发送Consumer Leader信息
Broker协调者发送Consumer Leader信息

接下来消费者集群的Leader节点会根据负载均衡策略的不同将分片节点分配策略发送给Broker协调节点进行核验是否合理:

消费者Leader节点发送节点分配方案
消费者Leader节点发送节点分配方案

在分配方案核验通过后,Consumer节点就会持续向Broker协调者发送心跳请求,来实时更新服务节点信息了:

Consumer节点发送心跳请求
Consumer节点发送心跳请求

至此,Kafka消费者集群负载均衡的大致实现流程就介绍完毕了,其本质还是在于Broker协调者的选举消费者Leader负载均衡策略的分配

而Kafka消费如此之快的原因也介绍得差不多了,总结起来就是这几个方面:

Producer(生产者端):批量发送消息,消息压缩

Broker(服务集群):顺序写策略,索引文件,零拷贝,mmap策略

Consumer(消费者端):Rebalance策略

那么我们到这里思考一下,为什么Kafka消息吞吐量最高可达17w+/s,但是参考了Kafka架构的RocketMQ吞吐量只有10w+/s,其原因就在于:

Kafka使用了senfile来减少文件拷贝次数,而RocketMQ使用mmap减少文件拷贝次数,这样的话,RocketMQ就必须要多进行两次用户态到内核态切换一次文件拷贝,也因此造成了性能的差异

好了,这就是本文的全部内容了,希望对你有所帮助!!!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Kafka的基础架构
  • Producer投递数据
    • 批量发送数据
    • 数据压缩
  • Broker处理消息
    • 顺序写文件
    • 索引文件
    • 零拷贝技术
    • mmap内核文件映射
  • Consumer选择服务集群获取消息
    • Rebalance重新分配分片节点
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档