揭秘Kafka高吞吐量的原理

Kafka 以其卓越的伸缩性和容错能力,确立了在分布式系统中的核心地位,成为消息队列领域的佼佼者。接下来,我将带领大家深入了解 Kafka 的高性能原理。在本节内容中,我们将介绍 Kafka 与 RocketMQ 之间的差异和联系,这将有助于我们更清晰地认识到 Kafka 的独特之处。同时,我们还将详细分析 Kafka 实现高性能的四个关键因素:磁盘顺序读写、页缓存、零拷贝技术以及批量处理机制。

Kafka 和 RocketMQ 对比

要介绍 Kafka,首先无法绕开的一个话题,那就是 Kafka 与 RocketMQ 的对比,因为 RocketMQ 的很多设计都参考了 Kafka 的架构;如果对 RocketMQ 原理不熟悉的读者可以看之前发过的文章

尽管 RocketMQ 在某些方面与 Kafka 相似,但由于它们解决的问题和业务出发点不同,我们不能简单地将它们划等号。我来举个形象的例子简单说一下二者本质的区别。

RocketMQ 很像是消防员使用的高压水枪,速度快、喷头比较小。而 Kafka 更像是急流,速度较高压水枪稍慢,但流量非常大。因此,每当我们提到 Kafka,总会和它的高吞吐量联系到一起。即使工作在普通的硬件上也能支持每秒数百万条消息的传输,并且 Kafka 天然支持 Hadoop 技术栈。

看到这里,也许你还会想,Kafka 能做的事情,RocketMQ 好像也可以做。为了帮你看清 Kafka 和普通消息队列的区别,我们还是从业务需求出发看个例子。

在许多大型企业中,业务数据量巨大,通常存储在 HDFS 或其他大数据存储介质中。面对如此庞大的数据量,如何快速将数据传递给下游系统是一个需要解决的问题。Kafka 的高性能特性使其在处理大规模数据传输时具有明显优势。例如,在磁盘上每小时产生数 TB 数据的场景下,我需要在 1 小时之内将这些数据批量搬走,显然这种情况下,我希望保证流速的情况下,"水流"的横截面积越大越好。这时,RocketMQ 很显然已经无法满足相关需求,而 Kafka 的高 吞吐能力就显现出了优势。

看到这里,你是否对 Kafka 性能的不可替代性已经有了基础的认识呢。那么,接下来,我将逐一带你分析 Kafka 高性能的关键点。

磁盘顺序读写

Kafka 是将消息记录持久化到本地磁盘中的,是的,你没听错,是磁盘。而且如果服务器不太好的话,甚至会选择机械硬盘。

此时,你可能会对 Kafka 的性能有所怀疑,人们普遍认为磁盘,尤其是廉价的机械磁盘,读写速度和内存有天壤之别,然而事实如何呢?以下截图来自 ACM 2009 年的论文《The Pathologies of Big Data》。

黄色的部分自上而下分别是,随机访问场景下机械磁盘、固态磁盘和内存的读写速度对比。这个对比结果还是很符合经验的:内存最快,固态磁盘次之,机械磁盘最差。然而在蓝色部分代表的顺序访问中,机械磁盘、固态磁盘和内存速度竟然差不多。

实际上不论什么存储介质,读写速度的核心在于访问的方式,也就是顺序读写还是随机读写。我们可以看到磁盘的随机读写很差,但是顺序读写比内存差不了多少。在某些服务器上盘顺序读写性能甚至要高于内存随机读写。

此外,现在的操作系统也针对磁盘的顺序读写做了大量优化,Kafka 就是基于磁盘的顺序读写来实现高性能的。Kafka 会把到来的消息不断追加到磁盘文件的末尾,这样 Kafka 的写入吞吐量就会极高。

上图是 Kafka 写入流程,其中每一个 Partition 是一个分区,可以理解为 RocketMQ 中的队列,每个 Partition 是磁盘上的一个文件,收到消息后,Kafka 会把数据插入到文件的末尾。

这种情况并不是很完美,当你需要删除数据的时候,就会破坏这个结构,那么怎么解决呢?Kafka 给出的答案就是,不删除数据。Kafka 会为每一个消费者保留一个偏移量,这点和 RocketMQ 几乎一样。

我们稍微回顾一下 RocketMQ 主题的实现,我们把下图中的队列换成 Partition,几乎就是 Kafka 了,是不是非常像。需要注意的是,Kafka 对 Partition 做了分段处理,这点很像 JDK7 时期的 ConcurrentHashMap,从而进一步提升了并发度。

当然如果一直不删除数据,硬盘肯定会被撑满,所以 Kakfa 支持基于时间和 Partition 文件大小来对历史数据做删除。具体可以去看它的配置文档。

页缓存

为了进一步优化读写性能,Kafka 还利用了操作系统本身的页缓存,也就是直接利用操作系统自身的内存而不是 JVM 内存。

这样我们可以绕开 GC,如果将数据放在 JVM 内存中,随着 JVM 中数据不断增多,垃圾回收将会变得复杂与缓慢,甚至出现 Stop The World 问题。

同时我们也绕开了对象的创建,我们知道对象有一些上下文、对象头等元数据信息,不使用对象也让数据的存储成本更小了,页缓存的空间利用率会更高,因为存储的都是紧凑的字节结构而不是独立的对象。

再者,即使程序进程重启,系统缓存依然不会消失,避免了重建缓存的过程。因此,通过直接引用操作系统的页缓存,Kafka 的读写速度得到了进一步提升。

零拷贝

那么,是否还有针对页缓存的其他优化方式呢?答案是肯定的。Linux 操作系统 "零拷贝"技术允许内核中页缓存直接发送到 Socket 缓冲区,这样绕开了内核态和用户态之间的数据交换。

我们先看看,如果 Kafka 不使用零拷贝技术,那么会经历这样的一个过程:

1.操作系统将数据从磁盘读入到内核态的页缓存中。

2.Kafka 从页缓存将数据拷贝到用户空间的内存中。

3.Kafka 将数据从用户空间内存再写回到内核态的 Socket 缓冲区中。

从图中可以看到,我们的数据在内核态和用户态之间移动了两次,那么能否避免这个过程呢?当然可以,Kafka 使用了零拷贝技术,也就是直接将数据从内核态的页缓存直接拷贝到内核态的 Socket 缓冲区,避免在内核态和用户态之间移动数据。

注意,这里的零拷贝并非指 0 次拷贝,而是避免了在内核态和用户态之间的拷贝。

批量操作

Kafka 提升性能的最后一个要素就是批量操作,Kafka 提供了很多批量操作 API 以提升吞吐量,在很多情况下,系统的瓶颈不是 CPU 或磁盘,而是网络 IO。

批量的数据传输可以高效地利用网络带宽,但是数据太多也会造成拥堵,Kafka 很自然地想到了批量压缩,Kafka 高性能的另一个原因在于,它把所有的消息都变成批量的文件,并且进行批量压缩,最大程度减少网络 IO 损耗。

总结

这次我们共同探索了 Kafka 实现高性能的秘诀。通过本篇内容,我们了解到 Kafka 采用批量处理机制来优化数据处理流程,并且通过磁盘顺序读写技术,以较低的成本实现了高效的性能表现。我们还揭示了操作系统中隐藏的两个强大工具:页缓存和零拷贝技术。希望大家不仅能够理解 Kafka 是如何达成其高性能的,而且能够在自己的项目中灵活运用这些关键技术。

相关推荐
安的列斯凯奇2 小时前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
架构文摘JGWZ3 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC3 小时前
Swift语言的网络编程
开发语言·后端·golang
邓熙榆3 小时前
Haskell语言的正则表达式
开发语言·后端·golang
专职6 小时前
spring boot中实现手动分页
java·spring boot·后端
指尖下的技术6 小时前
Kafka面试题----Kafka消息是采用Pull模式,还是Push模式
分布式·kafka
Ciderw6 小时前
Go中的三种锁
开发语言·c++·后端·golang·互斥锁·
m0_748246357 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
m0_748230447 小时前
创建一个Spring Boot项目
java·spring boot·后端
卿着飞翔7 小时前
Java面试题2025-Mysql
java·spring boot·后端