kafka一直以高吞吐量著称,在其高性能的背后,是多方面协作的结果,架构。分布式partition存储、ISR数据同步、以及高效利用磁盘/操作系统特性。而零拷贝技术正是kafka能够高效利用磁盘/操作系统特性核心。

零拷贝

零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。

传统IO流程

传统IO
  1. 将磁盘文件,拷贝到操作系统内核缓冲区
  2. 将内核缓冲区的数据,拷贝到应用程序的buffer
  3. 将应用程序buffer中的数据,再拷贝到socket网络发送缓冲区(属于内核缓冲区)
  4. 将socket buffer数据,拷贝到网卡,由网卡进行网络传输

传统IO方式,读取磁盘文件进行网络发送,经过4次数据拷贝。但是第2、3次的拷贝明显没有什么帮助。

传统IO存在多次无效拷贝,还伴随着大量的上下文切换。

MMAP优化

MMAP

这种方式使用mmap()代替了read()

  1. 磁盘的数据通过DMA拷贝到内核缓冲区
  2. 操作系统把这块内核缓冲区与应用程序共享,避免了用户缓冲区和内核缓冲区的跨界复制
  3. 应用程序调用write()直接从内核缓冲区的内容拷贝到socket缓冲区
  4. 最后系统将socket的数据传输到网卡,由网卡进行传输

MMAP减少了一次拷贝,提升了效率,但是并不减少上下文切换的次数。

Sendfile

sendfile

这种方式是使用sendfile代替了read+write操作

  1. 首先sendfile系统调用,通过DMA引擎将磁盘文件拷贝到内存缓冲区
  2. 在内核缓冲区,内核将数据拷贝到socket缓冲区
  3. 最后,DMA将数据从内核拷贝到网卡,由网卡传输

数据总共发生3次拷贝

使用DMA gather copy的sendfile

在内核2.4版本之后,sendfile可以在硬件支持的情况下实现更高效的传输。

DMA gather copy

在硬件的支持下,不再从内核缓冲区的数据拷贝到socket缓冲区,仅仅是缓冲区文件描述符fd和数据长度的拷贝。

这种方式避免了最后一次拷贝,并且减轻了CPU的负担,省去了页缓存到socket缓冲区的CPU 拷贝。这样内核空间和用户空间已经不存在任何多余的拷贝了。前提是硬件和相关驱动程序支持DMA gather copy。

kafka零拷贝

在kafka中,是使用了sendfile调用减少了数据从磁盘读取到发送之间的多余的内核态和用户态之间的拷贝和上下文切换。

kafka高性能的原因总结

  1. partition顺序读写,避免了磁盘寻址,充分利用了磁盘的特性
  2. Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入
  3. Customer从broker读取数据,采用sendfile,将磁盘读取到内核缓冲区后,不再将拷贝数据,而是拷贝fd文件描述符和数据长度到socket buffer进行发送

mmap和sendfile总结

  1. 都是Linux内核提供,实现零拷贝的API
  2. sendfile是将读到内核缓冲区的数据,直接转到socket buffer,进行网络发送
  3. mmap是将磁盘文件读取到内核缓冲区后进行映射,和用户缓冲区共享数据,然后CPU在拷贝数据到socket buffer,进行网络发送

在消费消息时,RocketMQ使用mmap,kafka使用sendfile。