传统的拷贝过程
流程步骤:
(1)操作系统将数据从磁盘文件中读取到内核空间的页面缓存;
(2)应用程序将数据从内核空间读入用户空间缓冲区;
(3)应用程序将读到数据写回内核空间并放入socket缓冲区;
(4)操作系统将数据从socket缓冲区复制到网卡接口,此时数据才能通过网络发送。
此过程涉及到4次上下文切换以及4次数据的复制,但是这些过程中cpu数据完全没有进行变化,仅仅是磁盘数据复制到了网卡的缓冲区,在这种情况下,如果去掉流程2和3,减少用户空间和内核空间之间的切换,虽然减少了数据交换的次数,但是仍然存在数据多次复制的情况。
注:步骤1和4的数据传输是通过DMA引擎进行拷贝的
步骤2和3的数据传输是通过cpu进行拷贝的**
零拷贝技术
在DMA技术的基础上,可以实现绝对的零拷贝,DMA技术为(Direct Memory Access,直接存储器访问),不需要依赖cpu的大量中断负载,数据文件可以直接绕过CPU在各个层之间传输,外围设备可以直接通过DMA控制器访问内存。通过零拷贝技术,可以减去没有必要的数据复制操作,同时也减少了上下文切换的次数。
可以看出,零拷贝技术只需要将数据从硬盘中将磁盘文件数据复制到页面缓存一次,然后直接将数据才从页面缓存直接发送到网络中,避免了重复复制的操作
页面缓存:存储磁盘数据的内核缓存区域,是操作系统内核中的一部分,用于缓存从磁盘读取的数据,以提高文件系统的性能;由物理内存中的页面构成的,每个页面的大小通常为4KB或更大。当内核从磁盘读取数据时,它将数据存储在页面缓存中。如果其他进程需要访问相同的数据,内核可以直接从页面缓存中读取,而无需再次从磁盘读取数据,从而提高了读取性能。在零拷贝技术中,页面缓存是实现数据传输的一部分。当数据需要从磁盘传输到网络或其他设备时,内核可以直接将页面缓存中的数据发送到目标设备,而无需将数据复制到用户空间或其他中间缓冲区。这样可以减少数据复制的次数和数据传输的延迟,提高数据传输的效率。
Java采用的零拷贝技术TransferTo方法底层也是调用sendfile(),Kafka的零拷贝技术可以直接将直接从它被调用的通道上传输到另一个可写字节通道,数据无需流经用户空间的应用程序;在Linux系统中零拷贝是基于sendfile()系统调用实现的,sendfile()实现的是读到内核空间的数据转到socket buffer,最后复制到协议引擎。
点击查看代码
mmap和sendfile都是零拷贝的实现方案,只不过不同的场景使用不同的方案;
mmap其实本质就是物理上磁盘文件的地址和页面缓存的地址进行映射,完成映射后对物理内存的操作会同步到硬盘上,用户通过修改内存就可以修改磁盘文件。Kafka生产的数据持久化到Broker里采用的是**mmap**文件映射的方式,实现顺序的快速写入
sendfile就是将读到内核空间的数据转到Socket Buffer,最后复制到协议引擎,相较于mmap又节省了一次cpu拷贝;
消费者从 broker 读取数据,采用sendfile,将磁盘文件读到内核缓冲区后,直接转到socket buffer进行网络发送