一、前言
RocketMQ为什么这么快、Kafka为什么这么快?用了零拷贝技术?什么是零拷贝技术,它们二者的零拷贝技术有不同吗?
二、为什么需要零拷贝
在计算机产业中,I/O的速度相较CPU,总是太慢的。SSD硬盘的IOPS可以达到2W、4W,但是我们CPU的主频有2GHz以上,也就意味着每秒会有20亿次的操作。如果对于I/O操作,都是由CPU发出对应的指令,然后等待I/O设备完成操作之后返回,那CPU有大量的时间其实都是在等待I/O设备完成操作。但是,这个 CPU 的等待,在很多时候,其实并没有太多的实际意义。我们对于 I/O 设备的大量操作,其实都只是把内存里面的数据,传输到 I/O 设备而已。在这种情况下,其实 CPU 只是在傻等而已。特别是当传输的数据量比较大的时候,比如进行大文件复制,如果所有数据都要经过 CPU,实在是有点儿太浪费时间了。因此计算机工程是们就发明了DMA技术,也就是直接内存访问(Direct Memory Access)技术,来减少CPU等待的时间。
DMA技术
本文不做过多相关介绍,这里我简单总结下对它的理解。
如上所述,CPU资源很宝贵,如果用它来处理I/O那么将是极大的损失,比如说我们用千兆网卡或者硬盘传输大量数据的时候,如果都用CPU来搬运的话,肯定是忙不过来,所以可以选择DMAC(DMA控制器即DMA Controller,简称DMAC),CPU告诉DMAC它需要传输什么数据,从哪里传输,传输到哪里去这些信息,然后交给DMAC去做,DMAC可以等到数据都到齐了,再发送信号,交给CPU去处理,而不是让CPU在哪里忙等待。(DMAC:我们不加工数据,只是数据的搬运工)
具体传输过程(从磁盘传输到网络)如图:
零拷贝
如上我们发现,虽然通过DMA技术能够使得CPU不用忙等待I/O操作,减轻了一些压力,但是从图中也能清晰地看出,两次CPU的Copy完全是在搞笑的,能不能把这两个步骤去掉呢?这就是零拷贝需要做的事情了,而我们熟知的RocketMQ、Kafka都是使用了零拷贝技术来优化I/O,而它们的零拷贝处理方式却有些不同。
Kafka零拷贝——SendFile
Kafka的代码调用了Java NIO库,具体是FileChannel里面的transferTo方法(底层是。我们的数据并没有读到中间的应用内存里面,而是直接通过Channel,写入到对应的网络设备里。并且对于Socket的操作,也不是写入到Socket的Buffer里面,而是直接根据描述符(Descriptor)写入到网卡的缓冲区里面。于是,在这个过程中,只进行了两次数据传输。(由于没有在用户态内存层里面去Copy数据,干掉了两次CPU的Copy,所以我们将之称为零拷贝(Zero-Copy)
SendFile的工作原理
系统调用sendfile()通过DMA把磁盘数据拷贝到kernel buffer(read buffer),然后数据被kernel直接拷贝到另外一个与socket相关的kernel buffer(socket buffer)。这样就没有用户态和内核态之间的切换,从内核中直接完成了从一个buffer到另一个buffer的拷贝,因为数据就在kernel里。
如图:
第一次,是通过 DMA,从硬盘直接读到操作系统内核的读缓冲区里面。第二次,则是根据 Socket 的描述符信息,直接从读缓冲区里面,写入到网卡的缓冲区里面。
这是Kafka目前实时数据传输管道的标准解决方案,也是Kafka高吞吐的秘密之一,零拷贝。
RocketMQ零拷贝——Mmap
Mmap全称Memory Mapped Files。简单描述其作用就是:将磁盘文件映射到内存,用户通过修改内存就能修改磁盘文件。
它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射,完成映射之后你对物理内存的操作会被同步到磁盘上(操作系统在适当的时候)。
通过mmap也有一个很明显的缺陷——不可靠,写到mmap中的数据并没有被真正地写到磁盘,操作系统会在程序主动调用flush的时候才把数据真正写到磁盘。
RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(正因为需要使用内存映射机制,故RocketMQ的文件存储都使用定长结构来存储,方便一次将整个文件映射至内存)。
如图:
总结
- CPU比I/O性能好很多,应当尽力给CPU让步,让它去做更多的事情,于是就有了DMA。DMA对CPU说,"你告诉我搬什么数据,搬到哪里,搬好了我告诉你,你先去忙别的"
- 我们发现,在数据传输过程中,有两次CPU的Copy可以省去,于是就有了内存映射技术(Mmap)、SendFile技术(内核数据Copy),让CPU不需要再白忙活了
- 市面上熟知的优秀中间件如RocketMQ使用的零拷贝技术是Mmap、Kafka使用的则是SendFile
参考资料:
《极客时间——深入浅出计算机组成原理》
《Kafka顺序读写与零拷贝(kafka为什么这么快)》