文章收录地址:Java-Bang 专注于系统架构、高可用、高性能、高并发类技术分享
除了消息顺序追加、页缓存等技术,Kafka 还使用零拷贝技术来进一步提升性能。所谓的零拷贝是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之手。零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换。对 Linux 操作系统而言,零拷贝技术依赖于底层的 sendfile() 方法实现。对应于 Java 语言,FileChannal.transferTo() 方法的底层实现就是 sendfile() 方法。
单纯从概念上理解“零拷贝”比较抽象,这里简单地介绍一下它。考虑这样一种常用的情形:你需要将静态内容(类似图片、文件)展示给用户。这个情形就意味着需要先将静态内容从磁盘中复制出来放到一个内存 buf 中,然后将这个 buf 通过套接字(Socket)传输给用户,进而用户获得静态内容。这看起来再正常不过了,但实际上这是很低效的流程,我们把上面的这种情形抽象成下面的过程:
代码语言:javascript复制read(file, tmp_buf, len);
write(socket, tmp_buf, len);
首先调用 read() 将静态内容(这里假设为文件 A )读取到 tmp_buf,然后调用 write() 将 tmp_buf 写入 Socket,如下图所示。
在这个过程中,文件 A 经历了4次复制的过程:
- 调用 read() 时,文件 A 中的内容被复制到了内核模式下的 Read Buffer 中。
- CPU 控制将内核模式数据复制到用户模式下。
- 调用 write() 时,将用户模式下的内容复制到内核模式下的 Socket Buffer 中。
- 将内核模式下的 Socket Buffer 的数据复制到网卡设备中传送。
从上面的过程可以看出,数据平白无故地从内核模式到用户模式“走了一圈”,浪费了2次复制过程:第一次是从内核模式复制到用户模式;第二次是从用户模式再复制回内核模式,即上面4次过程中的第2步和第3步。而且在上面的过程中,内核和用户模式的上下文的切换也是4次。
如果采用了零拷贝技术,那么应用程序可以直接请求内核把磁盘中的数据传输给 Socket,如下图所示。
零拷贝技术通过 DMA(Direct Memory Access)技术将文件内容复制到内核模式下的 Read Buffer 中。不过没有数据被复制到 Socket Buffer,相反只有包含数据的位置和长度的信息的文件描述符被加到 Socket Buffer 中。DMA 引擎直接将数据从内核模式中传递到网卡设备(协议引擎)。这里数据只经历了2次复制就从磁盘中传送出去了,并且上下文切换也变成了2次。零拷贝是针对内核模式而言的,数据在内核模式下实现了零拷贝。
要掌握Kafka的核心实现原理,不仅仅只是Kafka的日志格式、日志索引、日志清理等方面的内容,也包含底层物理存储相关的知识点。这样才能对 Kafka 的整理实现脉络有比较清晰的认知。加油吧~打工人!