揭秘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 是如何达成其高性能的,而且能够在自己的项目中灵活运用这些关键技术。

相关推荐
uzong5 小时前
技术故障复盘模版
后端
GetcharZp5 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi6 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国7 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy7 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack8 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9659 小时前
pip install 已经不再安全
后端
寻月隐君9 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github