传统方式实现:先读取磁盘、再用 socket 发送,实际也是进过四次 copy
buffer = File.read
Socket.send(buffer)
这一过程可以类比上边的生产消息:
首先通过系统调用将文件数据读入到内核态 Buffer(DMA 拷贝)
然后应用程序将内存态 Buffer 数据读入到用户态 Buffer(CPU 拷贝)
接着用户程序通过 Socket 发送数据时将用户态 Buffer 数据拷贝到内核态 Buffer(CPU 拷贝)
最后通过 DMA 拷贝将数据拷贝到 NIC Buffer
Linux 2.4+ 内核通过 sendfile 系统调用,提供了零拷贝。数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer,无需 CPU 拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件 - 网络发送由一个 sendfile 调用完成,整个过程只有两次上下文切换,因此大大提高了性能。
Kafka 在这里采用的方案是通过 NIO 的 transferTo/transferFrom 调用操作系统的 sendfile 实现零拷贝。总共发生 2 次内核数据拷贝、2 次上下文切换和一次系统调用,消除了 CPU 数据拷贝