kafka零拷贝原理


写在前面

这个问题经常在面试中出现,本文将详细介绍它的底层原理

零拷贝是什么意思

零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。通常是说在 IO 读写过程中。

实际上,零拷贝是有广义和狭义之分,目前我们通常听到的零拷贝,包括上面这个定义减少不必要的拷贝次数都是广义上的零拷贝。其实了解到这点就足够了。

我们知道,减少不必要的拷贝次数,就是为了提高效率。那零拷贝之前,是怎样的呢?

聊聊传统 IO 流程

比如:读取文件,再用 socket 发送出去。

传统方式实现:

先读取、再发送,实际经过 1~4 四次 copy。

分别是:

  • 第一次:将磁盘文件,读取到操作系统内核缓冲区;
  • 第二次:将内核缓冲区的数据,copy 到 application 应用程序的 buffer;
  • 第三步:将 application 应用程序 buffer 中的数据,copy 到 socket 网络发送缓冲区(属于操作系统内核的缓冲区);
  • 第四次:将 socket buffer 的数据,copy 到网卡,由网卡进行网络传输。


传统方式,读取磁盘文件并进行网络发送,经过的四次数据 copy 是非常繁琐的。实际 IO 读写,需要进行 IO 中断,需要 CPU 响应中断(带来上下文切换),尽管后来引入 DMA 来接管 CPU 的中断请求,但四次 copy 是存在“不必要的拷贝”的。

kafka零拷贝

kafka 作为 MQ 也好,作为存储层也好,无非是两个重要功能,一是 Producer 生产的数据存到 broker,二是 Consumer 从 broker 读取数据;我们把它简化成如下两个过程:

  • 网络数据持久化到磁盘 (Producer 到 Broker)
  • 磁盘文件通过网络发送(Broker 到 Consumer)

数据写入

Kafka 会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度 Kafka 采用了两个技术, 顺序写入和 MMFile(Memory Mapped File)。

顺序写入

磁盘读写的快慢取决于你怎么使用它,也就是顺序读写或者随机读写。在顺序读写的情况下,磁盘的顺序读写速度和内存持平。

因为硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动作”,它是最耗时的。

所以硬盘最讨厌随机 I/O,最喜欢顺序 I/O。为了提高读写硬盘的速度,Kafka 就是使用顺序 I/O。

而且 Linux 对于磁盘的读写优化也比较多,包括 read-ahead 和 write-behind,磁盘缓存等。

MMFile

即便是顺序写入硬盘,硬盘的访问速度还是不可能追上内存。所以 Kafka 的数据并不是实时的写入硬盘 ,它充分利用了现代操作系统分页存储来利用内存提高 I/O 效率。

Memory Mapped Files(后面简称 mmap)也被翻译成内存映射文件 ,在 64 位操作系统中一般可以表示 20G 的数据文件,它的工作原理是直接利用操作系统的 Page 来实现文件到物理内存的直接映射。

完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。

通过 mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),也不必关心内存的大小,有虚拟内存为我们兜底。

使用这种方式可以获取很大的 I/O 提升,省去了用户空间到内核空间复制的开销。(调用文件的 Read 会把数据先放到内核空间的内存中,然后再复制到用户空间的内存中)

但也有一个很明显的缺陷——不可靠,写到 mmap 中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用 Flush 的时候才把数据真正的写到硬盘。

Kafka 提供了一个参数 producer.type 来控制是不是主动 Flush:

如果 Kafka 写入到 mmap 之后就立即 Flush,然后再返回 Producer 叫同步 (Sync)。

如果 Kafka 写入 mmap 之后立即返回 Producer 不调用 Flush 叫异步 (Async)。

sendfile

数据写入只是kafka优化数据持久化的速度,跟零拷贝没有关系,零拷贝主要是通过操作系统提供的sendfile系统调用实现的。

Linux 2.4+ 内核通过 sendfile 系统调用,提供了零拷贝。磁盘数据通过 DMA(Direct Memory Access) 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer(socket buffer),无需 CPU 拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件 - 网络发送由一个 sendfile 调用完成,整个过程只有两次上下文切换,因此大大提高了性能。零拷贝过程如下图所示。

运行流程如下:

  • Sendfile 系统调用,文件数据被 Copy 至内核缓冲区。
  • 再从内核缓冲区 Copy 至内核中 Socket 相关的缓冲区。
  • 最后再 Socket 相关的缓冲区 Copy 到协议引擎。

相较传统 Read/Write 方式,2.1 版本内核引进的 Sendfile 已经减少了内核缓冲区到 User 缓冲区,再由 User 缓冲区到 Socket 相关缓冲区的文件 Copy。

而在内核版本 2.4 之后,文件描述符结果被改变,Sendfile 实现了更简单的方式,再次减少了一次 Copy 操作。

Kafka 把所有的消息都存放在一个一个的文件中,当消费者需要数据的时候 Kafka 直接把文件发送给消费者,配合 mmap 作为文件读写方式,直接把它传给 Sendfile。

可见零拷贝减少了系统内核到cpu的拷贝。并且从文件到系统内核的拷贝利用了mmap技术,加快了速度。因此可以实现快速文件通过网络发送。

总结

kafka 总结

  • partition 顺序读写,充分利用磁盘特性,这是基础;
  • Producer 生产的数据持久化到 broker,采用 mmap 文件映射,实现顺序的快速写入;
  • Customer 从 broker 读取数据,采用 sendfile,将磁盘文件读到 OS 内核缓冲区后,直接转到 socket buffer 进行网络发送。

mmap 和 sendfile 总结

  • 都是 Linux 内核提供、实现零拷贝的 API;
  • sendfile 是将读到内核空间的数据,转到 socket buffer,进行网络发送;
  • mmap 将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上。

参考

1] 【Kafka】一文详解零拷贝原理


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录