乐趣区

大数据开发最火技术Kafka背后的黑科技

Kafka 是由 Apache 软件基金会开发的一个开源流处理平台,被广泛地应用在数据缓冲、异步通信、汇集日志、系统解耦等方面。相比较于其他常见消息系统,Kafka 在保障了大部分功能特性的同时,还在高吞吐、低延迟等方面有很突出的表现。这篇文章不同于其他介绍 Kafka 使用或实现的文章,只是谈谈 Kafka 用了什么“黑科技”使他在性能方面有这么突出的表现。

消息顺序写入磁盘
磁盘大多数都还是机械结构(SSD 不在讨论的范围内),如果将消息以随机写的方式存入磁盘,就需要按柱面、磁头、扇区的方式寻址,寻址是一个“机械动作”也最耗时。为了提高读写硬盘的速度,Kafka 就是使用顺序 I /O。

图 1 Kafka 顺序 IO

上图中,每个 partition 就是一个文件,每条消息都被 append 到该 partition 中,属于顺序写磁盘,因此效率非常高。这种方法有一个缺陷—— 没有办法删除数据,所以 Kafka 是不会删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个 Topic 都有一个 offset 用来表示读取到了第几条数据。

关于磁盘顺序读写和随机读写的性能,引用一组 Kafka 官方给出的测试数据(Raid-5,7200rpm):

Sequence I/O: 600MB/s

Random I/O: 100KB/s

所以通过只做 Sequence I/O,给 Kafka 带来了性能的极大提升。

Zero Copy
考虑一个 web 程序读取文件内容并传输到网络的场景,实现的核心代码如下:

图 2 普通 read 方法

虽然只是两个调用,但却经过了 4 次 copy,其中有 2 次 cpu copy,还有多次用户态与内核态的上下文切换,这会加重 cpu 的负担,而零拷贝就是为了解决这种低效。

mmap:

减少拷贝次数的一种方法是调用 mmap()来代替 read()调用:

应用程序调用 mmap(),磁盘上的数据会通过 DMA 被拷贝到内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用 write(), 操作系统直接将内核缓冲区的内容拷贝到 socket 缓冲区中,最后再把数据发到网卡去。

图 3 mmap 方法

使用 mmap 可以减少一次 cpu copy,但也会遇到一些陷阱,当你的程序 map 了一个文件,但是当这个文件被另一个进程截断 (truncate) 时, write 系统调用会因为访问非法地址而被 SIGBUS 信号终止。通常可以通过,为 SIGBUS 信号建立信号处理程序或使用文件租凭(file leasing)的方式去解决,这里就不再赘述了。

sendfile:

从 2.1 版内核开始,Linux 引入了 sendfile 来简化操作


图 4 sendfile 方法

sendfile() 方法引发 DMA 引擎将文件内容拷贝到一个读取缓冲区(DMA copy)然后由内核将数据拷贝到 socket buffer(cpu copy)最后再拷贝到网卡(DMA copy)使用 sendfile 不仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在 kernel space

聊到这里,sendfile 至少还需要一次 cpu copy,那么这一步能不能省去呢?为了消除内核完成的所有数据复制,我们需要一个支持收集 (gather) 操作的网络接口。同时,在内核版本 2.4 中,也修改了套接字缓冲区描述符以适应零拷贝要求。这种方法不仅减少了多个上下文切换,还完全取消了 cpu copy。

图 5 sendfile 方法(DMA gather)

sendfile 系统调用利用 DMA 引擎将文件内容拷贝到内核缓冲区去,然后将带有文件位置和长度信息的缓冲区描述符添加 socket 缓冲区去,这一步不会将内核中的数据拷贝到 socket 缓冲区中,DMA 引擎会将内核缓冲区的数据拷贝到协议引擎中去,避免了最后一次 CPU 拷贝。

零拷贝技术非常普遍,JAVA 的 transferTo、transferFrom 方法就是 Zero Copy。

退出移动版