learn-tech/专栏/消息队列高手课/15Kafka如何实现高性能IO?.md
2024-10-16 06:37:41 +08:00

119 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
15 Kafka如何实现高性能IO
你好,我是李玥。
Apache Kafka 是一个高性能的消息队列在众多消息队列产品中Kafka 的性能绝对是处于第一梯队的。我曾经在一台配置比较好的服务器上,对 Kafka 做过极限的性能压测Kafka 单个节点的极限处理能力接近每秒钟 2000 万条消息,吞吐量达到每秒钟 600MB。
你可能会问Kafka 是如何做到这么高的性能的?
我们在专栏“进阶篇”的前几节课,讲的知识点一直围绕着同一个主题:怎么开发一个高性能的网络应用程序。其中提到了像全异步化的线程模型、高性能的异步网络传输、自定义的私有传输协议和序列化、反序列化等等,这些方法和优化技巧,你都可以在 Kafka 的源代码中找到对应的实现。
在性能优化方面除了这些通用的性能优化手段之外Kafka 还有哪些“独门绝技”呢?
这节课,我来为你一一揭晓这些绝技。
使用批量消息提升服务端处理能力
我们知道,批量处理是一种非常有效的提升系统吞吐量的方法。在 Kafka 内部,消息都是以“批”为单位处理的。一批消息从发送端到接收端,是如何在 Kafka 中流转的呢?
我们先来看发送端,也就是 Producer 这一端。
在 Kafka 的客户端 SDK软件开发工具包Kafka 的 Producer 只提供了单条发送的 send() 方法并没有提供任何批量发送的接口。原因是Kafka 根本就没有提供单条发送的功能,是的,你没有看错,虽然它提供的 API 每次只能发送一条消息但实际上Kafka 的客户端 SDK 在实现消息发送逻辑的时候,采用了异步批量发送的机制。
当你调用 send() 方法发送一条消息之后无论你是同步发送还是异步发送Kafka 都不会立即就把这条消息发送出去。它会先把这条消息,存放在内存中缓存起来,然后选择合适的时机把缓存中的所有消息组成一批,一次性发给 Broker。简单地说就是攒一波一起发。
在 Kafka 的服务端,也就是 Broker 这一端,又是如何处理这一批一批的消息呢?
在服务端Kafka 不会把一批消息再还原成多条消息再一条一条地处理这样太慢了。Kafka 这块儿处理的非常聪明,每批消息都会被当做一个“批消息”来处理。也就是说,在 Broker 整个处理流程中,无论是写入磁盘、从磁盘读出来、还是复制到其他副本这些流程中,批消息都不会被解开,一直是作为一条“批消息”来进行处理的。
在消费时消息同样是以批为单位进行传递的Consumer 从 Broker 拉到一批消息后,在客户端把批消息解开,再一条一条交给用户代码处理。
比如说,你在客户端发送 30 条消息,在业务程序看来,是发送了 30 条消息,而对于 Kafka 的 Broker 来说,它其实就是处理了 1 条包含 30 条消息的“批消息”而已。显然处理 1 次请求要比处理 30 次请求要快得多。
构建批消息和解开批消息分别在发送端和消费端的客户端完成,不仅减轻了 Broker 的压力,最重要的是减少了 Broker 处理请求的次数,提升了总体的处理能力。
这就是 Kafka 用批量消息提升性能的方法。
我们知道,相比于网络传输和内存,磁盘 IO 的速度是比较慢的。对于消息队列的服务端来说,性能的瓶颈主要在磁盘 IO 这一块。接下来我们看一下Kafka 在磁盘 IO 这块儿做了哪些优化。
使用顺序读写提升磁盘 IO 性能
对于磁盘来说,它有一个特性,就是顺序读写的性能要远远好于随机读写。在 SSD固态硬盘顺序读写的性能要比随机读写快几倍如果是机械硬盘这个差距会达到几十倍。为什么呢
操作系统每次从磁盘读写数据的时候,需要先寻址,也就是先要找到数据在磁盘上的物理位置,然后再进行数据读写。如果是机械硬盘,这个寻址需要比较长的时间,因为它要移动磁头,这是个机械运动,机械硬盘工作的时候会发出咔咔的声音,就是移动磁头发出的声音。
顺序读写相比随机读写省去了大部分的寻址时间,它只要寻址一次,就可以连续地读写下去,所以说,性能要比随机读写要好很多。
Kafka 就是充分利用了磁盘的这个特性。它的存储设计非常简单,对于每个分区,它把从 Producer 收到的消息,顺序地写入对应的 log 文件中,一个文件写满了,就开启一个新的文件这样顺序写下去。消费的时候,也是从某个全局的位置开始,也就是某一个 log 文件中的某个位置开始,顺序地把消息读出来。
这样一个简单的设计,充分利用了顺序读写这个特性,极大提升了 Kafka 在使用磁盘时的 IO 性能。
接下来我们说一下 Kafka 是如何实现缓存的。
利用 PageCache 加速消息读写
在 Kafka 中,它会利用 PageCache 加速消息读写。PageCache 是现代操作系统都具有的一项基本特性。通俗地说PageCache 就是操作系统在内存中给磁盘上的文件建立的缓存。无论我们使用什么语言编写的程序,在调用系统的 API 读写文件的时候,并不会直接去读写磁盘上的文件,应用程序实际操作的都是 PageCache也就是文件在内存中缓存的副本。
应用程序在写入文件的时候,操作系统会先把数据写入到内存中的 PageCache然后再一批一批地写到磁盘上。读取文件的时候也是从 PageCache 中来读取数据,这时候会出现两种可能情况。
一种是 PageCache 中有数据那就直接读取这样就节省了从磁盘上读取数据的时间另一种情况是PageCache 中没有数据,这时候操作系统会引发一个缺页中断,应用程序的读取线程会被阻塞,操作系统把数据从文件中复制到 PageCache 中,然后应用程序再从 PageCache 中继续把数据读出来,这时会真正读一次磁盘上的文件,这个读的过程就会比较慢。
用户的应用程序在使用完某块 PageCache 后,操作系统并不会立刻就清除这个 PageCache而是尽可能地利用空闲的物理内存保存这些 PageCache除非系统内存不够用操作系统才会清理掉一部分 PageCache。清理的策略一般是 LRU 或它的变种算法,这个算法我们不展开讲,它保留 PageCache 的逻辑是:优先保留最近一段时间最常使用的那些 PageCache。
Kafka 在读写消息文件的时候,充分利用了 PageCache 的特性。一般来说,消息刚刚写入到服务端就会被消费,按照 LRU 的“优先清除最近最少使用的页”这种策略,读取的时候,对于这种刚刚写入的 PageCache命中的几率会非常高。
也就是说,大部分情况下,消费读消息都会命中 PageCache带来的好处有两个一个是读取的速度会非常快另外一个是给写入消息让出磁盘的 IO 资源,间接也提升了写入的性能。
ZeroCopy零拷贝技术
Kafka 的服务端在消费过程中,还使用了一种“零拷贝”的操作系统特性来进一步提升消费的性能。
我们知道,在服务端,处理消费的大致逻辑是这样的:
首先,从文件中找到消息数据,读到内存中;
然后,把消息通过网络发给客户端。
这个过程中,数据实际上做了 2 次或者 3 次复制:
从文件复制数据到 PageCache 中,如果命中 PageCache这一步可以省掉
从 PageCache 复制到应用程序的内存空间中,也就是我们可以操作的对象所在的内存;
从应用程序的内存空间复制到 Socket 的缓冲区,这个过程就是我们调用网络应用框架的 API 发送数据的过程。
Kafka 使用零拷贝技术可以把这个复制次数减少一次,上面的 2、3 步骤两次复制合并成一次复制。直接从 PageCache 中把数据复制到 Socket 缓冲区中这样不仅减少一次数据复制更重要的是由于不用把数据复制到用户内存空间DMA 控制器可以直接完成数据复制,不需要 CPU 参与,速度更快。
下面是这个零拷贝对应的系统调用:
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。
如果你遇到这种从文件读出数据后再通过网络发送出去的场景,并且这个过程中你不需要对这些数据进行处理,那一定要使用这个零拷贝的方法,可以有效地提升性能。
小结
这节课,我们总结了 Kafka 的高性能设计中的几个关键的技术点:
使用批量处理的方式来提升系统吞吐能力。
基于磁盘文件高性能顺序读写的特性来设计的存储结构。
利用操作系统的 PageCache 来缓存数据,减少 IO 并提升读性能。
使用零拷贝技术加速消费流程。
以上这些,就是 Kafka 之所以能做到如此高性能的关键技术点。你可以看到,要真正实现一个高性能的消息队列,是非常不容易的,你需要熟练掌握非常多的编程语言和操作系统的底层技术。
这些优化的方法和技术,同样可以用在其他适合的场景和应用程序中。我希望你能充分理解这几项优化技术的原理,知道它们在什么情况下适用,什么情况下不适用。这样,当你遇到合适场景的时候,再深入去学习它的细节用法,最终就能把它真正地用到你开发的程序中。