概述
本文总结了Kafka的一些设计原则和思想。通过本文,可以了解,为什么kafka的读写性能如此之高,在设计时考虑了那些方面。
性能上的设计考虑
使用批量传输和读写
在效率上Kafka做了很大的努力。它考虑的主要场景是:高吞吐写和高并发读。也就是,在大量的写数据和多方读取数据的场景,kafka尽量使得读和写付出的代价最小。那么,它是如何做到的呢?
我们知道,大量小数量的网络I/O和服务器磁盘I/O会导致性能的降低。为了解决这个问题,kafka会将消息分组,形成”消息集”(message set)的抽象。这样网络请求将消息组合在一起,而不是一次发送单个消息。服务器会依次将大块的消息写入文件中,而消费者也会依次获取大块数据。这个简单的优化却产生了数量级的加速。批量数据传输导致更大的网络数据包,更大的顺序磁盘操作,连续的内存块等等,所有这些改变允许Kafka将一个突发的随机信息流转换为线性的写入流向消费者。
另一个低效率是在字节复制。在低负载时这不是一个问题,但在高负载下的影响是很大的。为了避免这种情况,我们在生产者(producer),broker和消费者(consumer)之间使用标准的二进制消息格式。
broker维护的消息日志本身仅仅是一个文件目录,每个文件由一组消息集填充,这些消息集以与生产者和消费者使用的格式相同的格式写入磁盘。维护这种通用格式允许优化最重要的操作:持久日志块的网络传输。现代Unix操作系统将数据从页面缓存到一个socket提供了一个高度优化的代码路径;在Linux下用sendfile系统调用。
为了理解sendfile,我们看一下一般的通过socket传输数据的路径: (1) OS从硬盘上把数据读取到kernel空间的pagecache (2) 应用程序从kernel空间读取数据到用户空间的缓冲区(user-space buffer) (3) 应用程序把写回到kernel空间的socket buffer (4) OS把数据从socket buffer复制到网卡的缓冲区中,通过网卡把数据发送出去
从以上的流程可以看出,一般的数据发送有4次数据copy和2次系统调用。而通过sendfile函数,可以把数据直接从OS的pagecache发送到网络。这样,调用路径得到了优化,数据直接被复制到NIC(网卡)的buffer中。 有关Java中sendfile和零拷贝支持的更多背景信息,请参阅此文章。
端到端的批量压缩(batch compression)
在数据传输时,往往处理的瓶颈往往不是CPU和磁盘,而是网络带宽。在广域网中传输数据,往往需要对数据进行压缩。你可以自己编写压缩函数对数据进行压缩。但这样做的结果往往是压缩率不高,或数据比较冗余,而且只能对消息进行一个一个的压缩,不能将多个消息批量压缩。 而高效的压缩需要将多个消息压缩在一起,而不是单独压缩每个消息。 Kafka提供数据的批量压缩。它会把一批数据一起压缩后发送到服务器。这批消息会以压缩的方式写入,并以压缩的方式进行保存,当数据被消费者消费的时候才进行解压。
Kafka支持GZIP、Snappy和LZ4压缩协议。
总结
本文描述了为了达到高性能,Kafka是如何设计的,设计时考虑了哪些方面。通过本文,我们可以大概有一个了解和认知。要详细查看Kafka是如何实现这些设计思想的,还需要进一步查看实现代码。
参考
- 官方的文档
- 《Kafka: The Definitive Guide》