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

2022-05-17 15:18:33 浏览数 (1)

为什么 Kafka 这么快

  • 批量处理
  • 客户端优化
  • 日志格式
  • 日志编码
  • 消息压缩
  • 建立索引,方便快速定位查询
  • 分区
  • 一致性
  • 顺序写盘
  • 页缓存
  • 零拷贝

以上几点是对于 kafka 为什么这么快总结的几个方面。

刚开始看零拷贝的时候,相信很多人。都一脸懵~,什么是零拷贝。零拷贝是怎么做到的。

Follow me!!!

零拷贝

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

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

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

聊聊传统 IO 流程

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

传统方式实现:

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

分别是:

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

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

kafka 的零拷贝

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

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

数据写入

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

顺序写入

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

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

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

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

如果在内存做这些操作的时候,一个是 Java 对象的内存开销很大,另一个是随着堆内存数据的增多,Java 的 GC 时间会变得很长。

Memory Mapped Files

即便是顺序写入硬盘,硬盘的访问速度还是不可能追上内存。所以 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)。

磁盘文件通过网络发送(Broker 到 Consumer)

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。

总结

kafka 总结

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

mmap 和 sendfile 总结

  1. 都是 Linux 内核提供、实现零拷贝的 API;
  2. sendfile 是将读到内核空间的数据,转到 socket buffer,进行网络发送;
  3. mmap 将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上。RocketMQ 在消费消息时,使用了 mmap。kafka 使用了 sendFile。

0 人点赞