为什么 Kafka 的吞吐量那么高?

在众多的消息中间件中,Kafka 的性能和吞吐量绝对是顶尖级别的,那么问题来了,Kafka 是如何做到高吞吐的 。在性能优化方面,它使用了哪些技巧呢?下面我们就来分析一下 。
以'批'为单位批量处理是一种非常有效的提升系统吞吐量的方法 , 操作系统提供的缓冲区也是如此 。在 Kafka 内部,消息处理是以"批"为单位的,生产者、Broker、消费者 , 都是如此 。
在 Kafka 的客户端 SDK 中 , 生产者只提供了单条发送的 send() 方法,并没有提供任何批量发送的接口 。原因是 Kafka 根本就没有提供单条发送的功能,是的你没有看错,虽然它提供的 API 每次只能发送一条消息,但实际上 Kafka 的客户端 SDK 在实现消息发送逻辑的时候,采用了异步批量发送的机制 。
当你调用 send() 方法发送一条消息之后,无论你是同步发送还是异步发送,Kafka 都不会立即就把这条消息发送出去 。它会先把这条消息,存放在内存中缓存起来,然后选择合适的时机把缓存中的所有消息组成一批,一次性发给 Broker 。简单地说 , 就是攒一波一起发 。
而 Kafka Broker 在收到这一批消息后 , 也不会将其还原成多条消息、再一条一条地处理 , 这样太慢了 。Kafka 会直接将"批消息"作为一个整体,也就是说,在 Broker 整个处理流程中,无论是写入磁盘、从磁盘读出来、还是复制到其他副本,在这些流程中,批消息都不会被解开,而是一直作为一条"批消息"来进行处理的 。
在消费时,消息同样是以批为单位进行传递的,消费者会从 Broker 拉到一批消息 。然后将批消息解开,再一条一条交给用户代码处理 。
比如生产者发送 30 条消息,在业务程序看来虽然是发送了 30 条消息,但对于 Kafka 的 Broker 来说,它其实就是处理了 1 条包含 30 条消息的"批消息"而已 。显然处理 1 次请求要比处理 30 次请求快得多,因为构建批消息和解开批消息分别在生产者和消费者所在的客户端完成 , 不仅减轻了 Broker 的压力 , 最重要的是减少了 Broker 处理请求的次数 , 提升了总体的处理能力 。
批处理只能算是一种常规的优化手段,它是通过减少网络 IO 从而实现优化 。而 Kafka 每天要处理海量日志,那么磁盘 IO 也是它的瓶颈 。并且对于处在同一个内网的数据中心来说,相比读写磁盘,网络传输是非常快的 。
接下来我们看一下,Kafka 在磁盘 IO 这块儿做了哪些优化 。
磁盘顺序读写我们知道 kafka 是将消息存储在文件系统之上的 , 高度依赖文件系统来存储和缓存消息 , 因此可能有人觉得这样做效率是不是很低呢?因为要和磁盘打交道 , 而且使用的还是机械硬盘 。
首先机械硬盘不适合随机读写,但如果是顺序读写,那么吞吐量实际上是不差的 。在 SSD(固态硬盘)上,顺序读写的性能要比随机读写快几倍,如果是机械硬盘,这个差距会达到几十倍 。因为操作系统每次从磁盘读写数据的时候,需要先寻址 , 也就是先要找到数据在磁盘上的物理位置,然后再进行数据读写 。如果是机械硬盘,这个寻址需要比较长的时间,因为它要移动磁头,这是个机械运动,机械硬盘工作的时候会发出咔咔的声音 , 就是移动磁头发出的声音 。
顺序读写相比随机读写省去了大部分的寻址时间,因为它只要寻址一次 , 就可以连续地读写下去,所以说性能要比随机读写好很多 。
而 kafka 正是利用了这个特性,任何发布到分区的消息都会被追加到 "分区数据文件" 的尾部,如果一个文件写满了,就创建一个新的文件继续写 。消费的时候,也是从某个全局的位置开始 , 也就是某一个 log 文件中的某个位置开始 , 顺序地把消息读出来 。这样的顺序写操作让 kafka 的效率非常高 。
使用 PageCache任何系统,不管大小,如果想提升性能,使用缓存永远是一个不错的选择,而 PageCache 就是操作系统在内存中给磁盘上的文件建立的缓存 , 它是由内核托管的 。无论我们使用什么语言 , 编写的程序在调用系统的 API 读写文件的时候,并不会直接去读写磁盘上的文件,应用程序实际操作的都是 PageCache,也就是文件在内存中缓存的副本 。
应用程序在写入文件的时候,操作系统会先把数据写入到内存中的 PageCache , 然后再一批一批地写到磁盘上 。读取文件的时候,也是从 PageCache 中来读取数据,这时候会出现两种可能情况 。


推荐阅读