11.Kafka 的零拷贝技术

目录

1.传统拷贝过程

2.DMA 技术的出现

3.零拷贝技术

4.Java零拷贝的实现


 在上一篇博文 10.Kafka 消息存储 中我们了解了 Kafka 内部消息是如何进行存储的。其中一个原因是 Kafka 的顺序写入机制,另外一个原因就是零拷贝(zero-copy)技术,这也是使用 Kafka 性能高的根本所在。接下来让我们简单来了解一下 Kafka 的零拷贝技术

1.传统拷贝过程

       首先我们先来了解一下传统的拷贝流程。当消息从发送到写入磁盘,Broker 维护的消息日志本身就是文件目录形式,每个文件都是二进制保存,生产者和消费者使用相同的格式来处理。在消费者获取消息时,服务器先从磁盘读取数据到内存,然后把内存中的数据原封不动的通过 socket 的形式发送给消费者。虽然这个操作看起来简单,但是实际上中间经历了很多步骤。如下图所示:

11.Kafka 的零拷贝技术_第1张图片

       这个过程涉及到 4 次上下文切换以及 4 次数据的复制,并且有两次复制操作是由 CPU 完成。但是这个过程中,数据完全没有进行变化,仅仅是从磁盘复制到网卡缓冲区。 

       在这种情况下,如果能够减少用户空间与内核空间之间的切换,是不是会比传统拷贝快一点呢?如下图:

11.Kafka 的零拷贝技术_第2张图片       结果显而易见,毕竟少了 1 次传输过程,肯定会比传统的拷贝性能高。这样子首先数据被从磁盘读取到 Read Buffer 中,然后再发送到 Socket Buffer,最后才发送到网卡。虽然减少了用户空间和内核空间之间的数据交换,但依然存在多次数据复制。

       明显性能的开销,都消耗在彼此之间的数据复制过程中,那么进一步减少数据的复制过程,或者干脆没有数据复制这一过程,性能会明显增强。这里就需要介绍到 DMA 技术 

2.DMA 技术的出现

       DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它的出现就是为了解决批量数据的输入/输出问题。它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。

       传统的内存访问,所有的请求都会发送到 CPU ,然后再由 CPU 来完成相关调度工作。如下图所示:

11.Kafka 的零拷贝技术_第3张图片

        当 DMA 技术的出现,数据文件在各个层之间的传输,则可以直接绕过CPU,使得外围设备可以通过DMA控制器直接访问内存。与此同时,CPU可以继续执行程序。如下图:

11.Kafka 的零拷贝技术_第4张图片

        在现代电脑中,很多硬件都是支持 DMA 技术的,这里面其中就包括我们此处用到的网卡。还有其他硬件也都是支持 DMA 技术的,例如:磁盘、显卡、声卡等其他硬件。

3.零拷贝技术

       有了 DMA 技术的,通过网卡直接去访问系统的内存,就可以实现现绝对的零拷贝了。这样就可以最大程度提高传输性能。通过“零拷贝”技术,我们可以去掉那些没必要的数据复制操作, 同时也会减少上下文切换次数。

       现代的 Unix 操作系统提供 了一个优化的代码路径,用于将数据从页缓存直接传输到 Socket; 在 Linux 中,是通过 sendfile 系统调用来完成的。Java 提供了访问这个系统调用的方法:FileChannel.transferTo API 。使用 sendfile ,只需要一次拷贝就行,允许操作系统将数据直接从页缓存发送到网络上。所以在这个优化的路径中, 只有最后一步将数据拷贝到网卡缓存中是需要的。

4.Java零拷贝的实现

File file = new File("demo.zip");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel fileChannel = raf.getChannel();
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("", 1234));
// 直接使用了transferTo()进行通道间的数据传输
fileChannel.transferTo(0, fileChannel.size(), socketChannel);

       Java的零拷贝由 FileChannel.transferTo() 方法实现。transferTo() 方法将数据从 FileChannel 对象传送到可写的字节通道(如Socket Channel等)。在内部实现中,由 native 方法 transferTo() 来实现,它依赖底层操作系统的支持。在 UNIX 和 Linux系统中,调用这个方法将会引起 sendfile() 系统调用。

    零拷贝的使用场景一般是:

  • 较大,读写较慢,追求速度
  • 内存不足,不能加载太大数据
  • 带宽不够,即存在其他程序或线程存在大量的IO操作,导致带宽不够

到这里,零拷贝技术介绍就告一段落了。

如果本文对你有所帮助,那就给我点个赞呗 ^_^

End

你可能感兴趣的:(Kafka)