在现代计算机系统中,零拷贝技术是一种优化方法,旨在最大限度地减少数据在内存和设备之间的复制操作,从而提高数据传输的效率。在 Rust 编程语言中,我们可以利用一些库和系统调用来实现零拷贝技术,包括 mmap + write、sendfile、sendfile + DMA 收集以及 splice 等。在本文中,我将为你总结这些技术,并提供相应的Rust示例代码。
在消息中间件 kafka 和 RocketMQ 中,也深度应用零拷贝技术进行优化,以达到较高的吞吐量,我在本文中也进行了总结。
mmap + write
mmap
(Memory Map)是一种在Linux系统中常用的内存映射技术,它允许你将一个文件或者其它设备的内容映射到进程的地址空间,使得这些内容在内存中可以被直接访问,而无需进行传统的读取或写入操作,通过这种方式,文件数据只会从磁盘 DMA 拷贝到内核空间,而无需从内核空间拷贝到用户空间。
sendfile
sendfile
方法是一种在两个文件描述符之间直接传输数据的系统调用,它避免了数据从内核空间到用户空间的复制,从而实现了零拷贝。
下面是一个使用nix
中的sendfile
方法进行文件传输的示例:
use nix::fcntl::{open, OFlag};
use nix::sys::sendfile::sendfile;
use nix::sys::stat::{stat, Mode};
fn main() -> Result {
let fstat = stat("src.txt")?;
let source_fd = open("src.txt", OFlag::O_RDONLY, Mode::empty())?;
let dest_fd = open("dst.txt", OFlag::O_WRONLY | OFlag::O_CREAT, Mode::empty())?;
let mut remain = fstat.st_size;
while remain > 0 {
sendfile(dest_fd, source_fd, None, 4096)?;
remain -= 4096;
}
Ok(())
}
sendfile + DMA 收集
DMA(Direct Memory Access)是一种数据传输技术,它允许外设直接访问内存,而无需 CPU 的干预。在 Linux 系统中,可以通过 sendfile 系统调用结合 DMA 收集功能,实现零拷贝的文件传输。
splice
splice
函数在 Linux 系统中用于在两个文件描述符之间进行数据传输,从而实现高效的数据移动,而无需在用户空间和内核空间之间进行数据复制。以下是一个使用nix
库进行splice
操作的示例:
首先使用stat()
方法获取了源文件(src.txt
)的详细信息,以获取文件大小。然后,调用pipe()
方法创建了一个匿名管道,获得了读取端和写入端的文件描述符(read_pipe
和write_pipe
)。分别打开了源文件和目标文件,循环通过管道零拷贝文件,把传输长度设置为页大小(4096字节),有助于优化传输性能。设置SPLICE_F_MOVE
标志位,可以提示内核尽量移动数据而不是拷贝数据。
use nix::fcntl::{open, OFlag, splice, SpliceFFlags};
use nix::unistd::pipe;
use nix::sys::stat::{stat, Mode};
fn main() -> Result {
let fstat = stat("src.txt")?;
let (read_pipe, write_pipe) = pipe()?;
let source_fd = open("src.txt", OFlag::O_RDONLY, Mode::empty())?;
let dest_fd = open("dst.txt", OFlag::O_WRONLY | OFlag::O_CREAT, Mode::empty())?;
let mut remain = fstat.st_size;
while remain > 0 {
splice(source_fd, None, write_pipe, None, 4096, SpliceFFlags::SPLICE_F_MOVE)?;
splice(read_pipe, None, dest_fd, None, 4096, SpliceFFlags::SPLICE_F_MOVE)?;
remain -= 4096;
}
Ok(())
}
kafka 性能优化技术总结
顺序读写
producer 每次会追加写入到 partition(对 segment 文件进行追加写),consumer 每次消费的时候,根据 offset 进行顺序读取,并且通过批量刷盘的方式来减少磁盘 I/O 的次数。
页缓存技术
kafka 利用 linux 的 page cache 技术,将页面缓存到内存中,并且通过异步落盘,减少磁盘 I/O 次数。如果发生服务器掉电,内存中的数据可能会丢失,通过 Replication 机制去解决数据丢失的问题。
零拷贝之 mmap
kafka 会给一些数据建立稀疏索引,稀疏索引的作用是帮助查找到数据的一个大致的范围,稀疏索引保存在文件内,因此 kafka 使用mmap
的方式来读取稀疏索引文件,减少在内核空间和用户空间中的拷贝次数。
零拷贝之 sendfile
consumer 的作用只是从磁盘上读取文件并通过网卡转发出去,不需要对这段数据进行修改,这非常适合使用sendfile
来进行 I/O。再加上 kafka 可能会在 page cache 中缓存一些未落盘的数据,或者预读一些可能用的数据到 page cache 中,所以效率更加高,这也是 kafka 支持百万级吞吐量的一个原因。
RocketMQ 性能优化技术总结
RocketMQ 的功能更完善、更全面,相比于 kafka,它支持定时消息、消息过滤等。也由于需要支持这些功能,因此要使文件内容对用户进程可见,不能使用sendfile
了。因此 RocketMQ 的吞吐量要低于 kafka。
mmap
RocketMQ 把收到的消息通过顺序写入记录到 commit log 中,并建立对应的 consumer queue(消息索引),consumer 通过 consumer queue 来获取消息。commit log 和 consumer queue 都是通过 mmap 的方式来读取的,所以 RocketMQ 采用定长结构来存储文件,方便一次性把文件映射到内存中。通过文件预热的方式来解决可能存在的缺页问题。