首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从新手到架构师:Web服务器如何实现大文件跨节点传输

从新手到架构师:Web服务器如何实现大文件跨节点传输

作者头像
早起的鸟儿有虫吃
发布2025-06-28 15:40:44
发布2025-06-28 15:40:44
6320
举报
图片
图片

知识地图:深入理解计算机系统--网络子系统

助力工作5-10年程序员进阶架构师,希望对你有帮助

一. 系统设计:Web服务器 如何实现大文件跨节点传输

1.1 先排除下面几个干扰

1、考虑阿里P7面试,我必须回答很完美,考虑各种特殊情况。

2、考虑刚入门,不懂各种概念,我必须准备各种不知道内容

3、考虑大文件多大文件,我必须拆分处理

4、遇到网络问题,我怎么保证一致性。

你这样努力思考,事实证明,一切都是徒劳的,不但不会被夸奖,

反而被批评成 想到哪里就是哪里,思路混乱,耗时耗力投入,没有结果

变成了别用行动上的勤奋,掩盖思考上的懒惰

你有点生气 必须接受,

排除这些 "难题"不去解决 然后还能干什么

1.2 听到这个问题第一感受是什么?

看过一个文章 HTTP 断点续传(分块传输)我了解, 但是让我按照他们做法 八股文说出,我不愿意,说不出口。

这就对了,这就是事实求是,这个程序员最宝贵特点

伟人也说了事实求是:

就是要讲真话,不偷、不装、不吹。偷就是偷东西,

装就是装样子,‘猪鼻子里插葱——装象’,吹就是吹牛皮。

为了解决 主要矛盾是什么,那就尝试

从自己伸手够得到地方开始,

第一直觉,不就是2个行代码实现

代码语言:javascript
复制
一个节点:
read(file, tmp_buf, len);//从磁盘读取出来
write(socket, tmp_buf, len);//通过网络发送出去
或者:
while((n = read(diskfd, buf, BUF_SIZE)) >)
    write(sockfd, buf , n);

一个节点: 
read(socket, tmp_buf, len);//通过网络读取数据
write(file, tmp_buf, len);//写磁盘

恭喜你 你完成第一个版本设计,方案明确,代码简单。

后端开发工程师(DE) 这有什么可恭喜的? 感觉没什么, 在简单代码也是方案

不是代码简单,是Liunx 平台强大(read write 操作系统完成了过程是什么?)

因为还没有结束,因为领导 看不懂代码

1.3 你必须绘制流程图出来

数据从哪里来的 磁盘 需要写出来,数据通过什么发送的,网卡,需要写出来

这就是闭环方案。

图片
图片

首先,期间共发生了 4 次用户态与内核态的上下文切换 还发生了 4 次数据拷贝, 其中两次是 DMA 的拷贝, 另外两次则是通过 CPU 拷贝的

图片
图片

1.4 主要矛盾

我们回过头看这个文件传输的过程, 我们只是搬运一份数据,结果却搬运了 4 次,过多的数据拷贝无疑会消耗 CPU 资源,大大降低了系统性能

我们知道上下文切换是因为用户空间没有权限操作磁盘或网卡,而只能在虚拟空间上进行。

相比之下,内核拥有最高权限,因此操作设备的任务都需要操作系统内核完成

文件传输 设计目标是什么 就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数

这就是大局观,主要矛盾,从一个简单流程设计发现问题。

这个时候可能马上考虑怎么优化, 优化成什么样子 先放一放。

主要矛盾还没有解决。

如何把用户需求转换系统设计,这是另外一个问题。这是系统工程师(SE)完成事情。

完成一次数据传输 ,大家都都感觉到4次上下文切换,4 次数据拷贝,怎么给出准确定义

4次上下文切换 我减少几次,减少用户--内核,内核还是到用户

4次数据拷贝,我减少几次,减少cpu负责的拷贝,还是 DMA拷贝?

避免数据拷贝

①避免操作系统内核缓冲区之间进行数据拷贝操作。 ②避免操作系统内核和用户应用程序地址空间这两者之间进行数据拷贝操作。 ③用户应用程序可以避开操作系统直接访问硬件存储。 ④数据传输尽量让 DMA 来做。

靠掷筛子决定吗?

图片
图片

CPU数据拷贝的次数是由于上下文切换导致CPU在用户态和内核态之间来回复制数据,这是没有必要的。 此外,用户缓冲区在整个传输过程中也是没有必要存在的。

1.5 自己绘制图的,自己了解一些概念(可选)

什么是系统调用,什么是DMA,大家都会遇到同样疑问,这个自己提前想一想,不等到别人问起来时候,你先查询,晚了。

谁参与系统调用?  CPU

当应用程序需要读取磁盘数据时,调用read()从用户态陷入内核态,read()这个系统调用最终由CPU来完成;

CPU向磁盘发起I/O请求,磁盘收到之后开始准备数据;

磁盘将数据放到磁盘缓冲区之后,向CPU发起I/O中断,报告CPU数据已经Ready了;

CPU收到磁盘控制器的I/O中断之后,开始拷贝数据,完成之后read()返回,再从内核态切换到用户态;

图片
图片
什么是 DMA控制器

DMA是什么

不通过CPU的内存访问

直接内存访问(Direct Memory Access) 是一种硬件设备绕开CPU独立直接访问内存的机制。

DMA不是新技术,

想想一下您正在电脑面前刷剧,CPU此时忙碌着处理视频播放事务的计算任务;

突然,您想把一个带有U盘(外设A)文件数据拷贝到您的电脑的某个磁盘(外设B)上,这就给CPU触发了一次中断,

需要CPU优先处理文件数据拷贝(复制),从而影响了CPU处理播放视频事务。

那有没有可能让数据从外设A拷贝到外设B时, 让CPU减少或甚至不参与消耗算力资源呢?

答案是肯定的!

图片
图片
图片
图片

DMA技术如何实现数据直接传输 DMA技术,在Linux OS如何应用?

而Linux OS提供了轮询、I/O 中断以及 DMA 传输这 3 种磁盘与主存之间的数据传输机制。

1)轮询方式是基于死循环对 I/O 端口进行不断检测。

2)I/O 中断方式是指当数据到达时,磁盘主动向 CPU 发起中断请求,由 CPU 自身负责数据的传输过程。

3)DMA 传输则在 I/O 中断的基础上引入了 DMA 磁盘控制器,由DMA 磁盘控制器负责数据的传输,降低了 I/O 中断操作对 CPU 资源的大量消耗。

本质上,DMA技术就是计算机主板上一块独立的芯片,当 计算机需要在内存和 I/O 设备进行数据传输的时候,不再需要CPU来执行耗时的IO操作, 而是通过DMA控制器来完成

目前支持DMA的硬件包括:网卡、声卡、显卡、磁盘控制器等。

图片
图片

有了DMA的参与之后的流程发生了一些变化:

图片
图片

最主要的变化是,CPU不再和磁盘直接交互,而是DMA和磁盘交互并且将数据从磁盘缓冲区拷贝到内核缓冲区,之后的过程类似。

【敲黑板】无论从仅CPU方式和DMA&CPU方式,都存在多次冗余数据拷贝和内核态&用户态的切换。

我们继续思考Web服务器读取本地磁盘文件数据再通过网络传输给用户的详细过程。

一次完成的数据交互包括几个部分:系统调用syscall、CPU、DMA、网卡、磁盘等。

5
5

系统调用syscall是应用程序和内核交互的桥梁,每次进行调用/返回就会产生两次切换:

调用syscall 从用户态切换到内核态

syscall返回 从内核态切换到用户态

DMA参与情况下也是如此
DMA参与情况下也是如此

DMA参与情况下也是如此

来看下完整的数据拷贝过程简图:

7
7

读数据过程:

应用程序要读取磁盘数据,调用read()函数从而实现用户态切换内核态,这是第1次状态切换;

DMA控制器将数据从磁盘拷贝到内核缓冲区,这是第1次DMA拷贝;

CPU将数据从内核缓冲区复制到用户缓冲区,这是第1次CPU拷贝;

CPU完成拷贝之后,read()函数返回实现用户态切换用户态,这是第2次状态切换;

写数据过程:

应用程序要向网卡写数据,调用write()函数实现用户态切换内核态,这是第1次切换;

CPU将用户缓冲区数据拷贝到内核缓冲区,这是第1次CPU拷贝;

DMA控制器将数据从内核缓冲区复制到socket缓冲区,这是第1次DMA拷贝;

完成拷贝之后,write()函数返回实现内核态切换用户态,这是第2次切换;

综上所述:

读过程涉及2次空间切换、1次DMA拷贝、1次CPU拷贝;

写过程涉及2次空间切换、1次DMA拷贝、1次CPU拷贝;

可见传统模式下,涉及多次空间切换和数据冗余拷贝,效率并不高,接下来就该零拷贝技术出场了。

1.5 DMA,TCP,RDMA区别

DMA与RDMA的前世今生

DMA(本地直接内存访问): 允许本地外设(如磁盘、网卡)直接访问主机内存,无需CPU参与数据搬运。CPU仅需初始化传输,后续由DMA控制器完成数据移动,显著降低CPU开销。

RDMA(远程直接内存访问): 在DMA基础上扩展至跨主机网络环境,允许一台主机的网卡直接读写另一台主机的内存,完全绕过双方CPU和操作系统内核,实现网络级零拷贝

二、 解决方案:零拷贝Zero-Copy技术

目前来看,零拷贝技术的几个实现手段包括:mmap+write、sendfile、sendfile+DMA收集、splice等。

图片
图片

Linux 主要提供了mmap()、sendfile()、splice()三个系统调用来避免数据在内核空间与用户空间进行不必要的拷贝

本文的零拷贝都是基于网络传输

零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。

直接 I/O

mmap方式

什么是mmap

mmap
mmap

mmap

mmap是Linux提供的一种内存映射文件的机制。

文件提供接口
文件提供接口

文件提供接口

mmap 把文件内容直接映射到用户空间的虚拟地址

磁盘 -> page cache(内核页缓存) <===> 用户态指针(共享同一物理页)

这样就减少了一次用户态和内核态的CPU拷贝,但是在内核空间内仍然有一次CPU拷贝。

mmap虚存区映射及读写性能探究-西邮本科生实验视频分享

还是不理解

图片
图片

整个过程发生了 4 次用户态和内核态的上下文切换和3 次拷贝(2次RDMA 1次cpu),具体流程如下:

1

用户进程通过mmap()方法向操作系统发起调用,上下文从用户态转向内核态

2

DMA 控制器把数据从硬盘中拷贝到读缓冲区

3

上下文从内核态转为用户态,mmap 调用返回

4

用户进程通过write()方法发起调用,上下文从用户态转为内核态

5.CPU 将读缓冲区中数据拷贝到 socket 缓冲区

1

DMA 控制器把数据从 socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,write()返回

疑惑:5. CPU 将读缓冲区中数据拷贝到 socket 缓冲区 为什么有这步骤呢?

🤔 为什么 CPU 要拷贝这一步?

mmap 返回的是一个用户空间映射区域(MMU 映射页面缓存),但是这些数据并未直接放在 socket 内核缓冲区中;

当你调用 write(),内核运行时必须将数据从用户缓冲区复制到socket 的内核缓冲区,以便后续由网络栈处理;

这一步是用户态→内核态的上下文切换后,由 CPU 执行 copy_to_kernel 的动作。

mmap的方式节省了一次 CPU 拷贝,同时由于用户进程中的内存是虚拟的, 只是映射到内核的读缓冲区,所以可以节省一半的内存空间,比较适合大文件的传输

疑问:为什么出现 socket 缓冲区 上面流程 是什么呀?

别忘记我们解决什么问题

buf = mmap(file, len);//用 mmap() 替换 read() 系统调用函数 write(sockfd, buf, len);

图片
图片

具体过程如下:

1

应用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝

2

应用进程再调用 write(),操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据;

当用户进程调用write(socket_fd, buf, len)时:

buf是mmap映射的虚拟地址,指向Page Cache中的数据。

- 内核仍需将Page Cache中的数据复制到Socket发送缓冲区,这个操作由CPU执行内存拷贝完成,而非DMA

1

最后,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由 DMA 搬运的。

2

我们可以得知,通过使用 mmap() 来代替 read(), 可以减少一次数据拷贝的过程。

优点:减少一次数据拷贝

传统 read/write 流程:磁盘数据 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡(4次拷贝,4次上下文切换

内核缓冲区 → Socket缓冲区 → 网卡(3次拷贝,4次上下文切换

缺点

1

mmap对大文件传输有一定优势,但是小文件可能出现碎片

2

但这还不是最理想的零拷贝, 因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,而且仍然需要 4 次上下文切换,因为系统调用还是 2 次

方式2 sendfile方式

图片
图片

sendfile系统调用是在 Linux 内核2.1版本中被引入,它建立了两个文件之间的传输通道。

函数如下:

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); 参数说明:

out_fd :目的地端的文件描述符

in_fd:源端的文件描述符

offset:源端的偏移量

count:复制的长度

返回实际复制数据的

Rust接口 更加明确 读取文件数据,传递到socket(不负责网卡)

代码语言:javascript
复制
///https://docs.rs/sendfile/latest/sendfile/fn.send_file.html
/// # Arguments
///
///  * `file` must be a regular file, i.e. [`File`], opened for reading.
///  * `socket` must be a socket, e.g. [`TcpStream`] or [`UdpSocket`], opened
///    for writing.
///
/// [`File`]: std::fs::File
/// [`TcpStream`]: std::net::TcpStream
/// [`UdpSocket`]: std::net::UdpSocket
///
/// # Unsafety
///
/// This function is unsafe because the caller must ensure that the provided
/// `file` and `socket` are usable in the `sendfile` system call. The
/// requirements for this system call are different between platforms.
pub unsafe fn send_file<F, S>(file: F, socket: S) -> SendFile<F, S> {
    SendFile {
        file,
        socket,
        written:,
    }
}

图片
图片

sendfile方式只使用一个函数就可以完成之前的read+write 和 mmap+write的功能,

这样就少了2次状态切换,由于数据不经过用户缓冲区,因此该数据无法被修改。

优点:

应用程序只需要调用sendfile函数即可完成,只有2次状态切换、1次CPU拷贝、2次DMA拷贝。

缺点:

sendfile在内核缓冲区和socket缓冲区仍然存在一次CPU拷贝,或许这个还可以优化。

endfile+DMA收集

Linux 2.4 内核对 sendfile 系统调用进行优化,但是需要硬件DMA控制器的配合。

升级后的sendfile将内核空间缓冲区中对应的数据描述信息(文件描述符、地址偏移量等信息)记录到socket缓冲区中。

DMA控制器根据socket缓冲区中的地址和偏移量将数据从内核缓冲区拷贝到网卡中,从而省去了内核空间中仅剩1次CPU拷贝。

12
12

12

这种方式有2次状态切换、0次CPU拷贝、2次DMA拷贝,但是仍然无法对数据进行修改, 并且需要硬件层面DMA的支持,并且sendfile只能将文件数据拷贝到socket描述符上,有一定的局限性。

啥意思 不经过 cpu 了

图片
图片

条件: 你可以在你的 Linux 系统通过下面这个命令,查看网卡是否支持 scatter-gather 特性:

$ ethtool -k eth0 | grep scatter-gather scatter-gather: on

第一步,通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;

第二步,缓冲区描述符和数据长度传到 socket 缓冲区, 这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里, 此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中, 这样就减少了一次数据拷贝;

零拷贝技术的文件传输方式相比传统文件传输的方式,

减少了 2 次上下文切换和数据拷贝次数,

只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,

而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

所以,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上

splice方式

splice系统调用是Linux 在 2.6 版本引入的,其不需要硬件支持,并且不再限定于socket上,实现两个普通文件之间的数据零拷贝。

14
14

splice系统调用可以在内核缓冲区和socket缓冲区之间建立管道来传输数据,避免了两者之间的 CPU 拷贝操作。

splice也有一些局限,它的两个文件描述符参数中有一个必须是管道设备。

本文通过介绍数据交互的基本过程、传统模式的缺点,进而介绍了零拷贝的一些实现方法。

适用:静态大文件传输(如视频、日志)

三、讨论 既然零拷贝这么厉害,大文件传输那就用他了?

3.1 先思考一个问题: 为什么 AIO 绕过 PageCache?

Linux 的传统异步 I/O 实现(尤其是 libaio)通常只支持 Direct I/O(O_DIRECT):

图片
图片

Q:PageCahe内存有限,如果我们读取一个大文件,PageCahe很快就会被占满,如果长时间暂用PageCahe,那么其他热点数据就无法使用到PageCahe的好处,会导致磁盘的性能降低,这时怎么办?

图片
图片

PageCache 的优点主要是两个:

缓存最近被访问的数据; 预读功能; 这两个做法,将大大提高读写磁盘的性能。

但是,在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DMA 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能

A:先说答案:异步IO + 直接IO这个情况我们应该想办法绕过PageCache,

大文件不应该使用PageCache直接IO会直接绕过PageCacheIO的读取是阻塞的,可以考虑使用异步IO去代替

所以,传输文件的时候,我们要根据文件的大小来使用不同的方式:

传输大文件的时候,使用「异步 I/O + 直接 I/O」;

传输小文件的时候,则使用「零拷贝技术」;

**libaio,SPDK,和io_uring的性能比较 **

相关资料:

https://www.bilibili.com/video/BV1nf421B78a

走进高性能世界:探索dpdk、spdk、网络协议栈、vpp、OvS、DDos、SDN、NFV和虚拟化,成为专业的技术大师!

图解原理|Linux I/O 神器之 io_uring

四、  不同产品对比

场景

推荐 I/O 模式

原因

小文件频繁访问

缓存 I/O

命中率高,响应快

大文件读取/分发

异步 I/O + 直接 I/O

避免 PageCache 污染

高并发数据传输

sendfile / splice

降低拷贝成本,提高吞吐

日志/数据库写入

O_DIRECT + io_uring

控制落盘,减少延迟

4.1 Nginx性能调优与零拷贝

在 nginx 中,我们可以用如下配置,来根据文件的大小来使用不同的方式:

location /video/ { sendfile on; //零拷贝技术 aio on; //异步 I/O + 直接 I/O directio 1024m; }

当文件大小大于 directio 值后,使用「异步 I/O + 直接 I/O」,否则使用「零拷贝技术」

参考:https://stackoverflow.com/questions/58066785/always-use-sendfile-with-nginx-on-linux

4.2  RocketMQ为什么使用mmap而不适应sendfile?

🎯 结论先行:

RocketMQ 使用 mmap 而不是 sendfile,是因为它需要支持高效的“随机读写”和“顺序刷盘”,而不是一次性的大文件传输。sendfile 更适合于纯文件转发型场景(如 Web 服务器),不适合消息队列的灵活读写模型。


🔍 原因分析:

✅ 1. mmap 支持按需、可控的随机读写

RocketMQ 的消息存储文件(CommitLog、ConsumeQueue 等):

实际上是内存映射的文件区域

支持顺序写入 + 多线程并发读取

使用 mmap 之后,应用进程可以像访问内存一样访问磁盘上的文件内容,不需要频繁的系统调用(如 read() / write())。

优点:

低开销随机访问(尤其是读取):内存地址直接访问;

配合 PageCache 实现操作系统自动的读写缓冲;

刷盘可控性强:通过 flush()fsync() 实现精确的持久化控制。


🚫 2. sendfile 只适合整块文件搬运,不适合灵活的消息系统

sendfile() 的设计目标是 “文件 ➝ 网络 socket 的零拷贝传输”,并不支持:

按消息粒度发送(比如 offset、msgId 定位);

读取指定位置的数据(随机读)

支持内存中暂未 flush 的脏数据

在 RocketMQ 的典型读路径中,消费者可能拉取:

某个 offset 对应的消息;

某个时间戳之后的消息;

某个主题+队列ID的消息段。

这类行为都无法通过 sendfile() 完成,因为:

sendfile(fd, sockfd, offset, count) 本质是顺序地“从某个 offset 开始连续读一段”,不支持任意跳转、解析和拷贝部分结构体字段(如消息长度、tag hash、topic name 等)。


⚖️ 3. 对比小结

特性

mmap

sendfile

是否支持随机读

❌ 只能顺序传

是否支持消息粒度控制

是否适合网络传输

⭕(需配合 write)

是否能避免一次用户态拷贝

✅(读写像操作内存)

✅(写 socket 零拷贝)

RocketMQ使用原因

高效的随机读写 + 操作系统缓存支持

不适用消息存储


🧠 补充:RocketMQ 的消息读写路径设计(简化)

写入消息

1

将消息序列化后直接 memcpy 到 mmap 映射区;

2

根据刷盘策略(同步/异步)决定何时调用 flush

读取消息

1

定位到 offset;

2

从 mmap 映射区按需解析、反序列化并打包;

3

通过 Netty 等方式发送给客户端(最终通过 write() 系统调用)。


✅ 总结一句话:

RocketMQ 使用 mmap 是为了高效支持随机访问、顺序写入和延迟刷盘等特性,而 sendfile 是为高效传输完整文件设计,无法满足消息队列对于灵活性和高并发的需求。


如果你想,我可以进一步画一张 mmap 读写 vs sendfile 传输 的对比流程图,用于分享或做 PPT 使用。是否需要?

4.3 3FS(DeepSeek自研文件系统)

3FS是幻方AI自研的高速读写文件系统,是幻方AIHPC“萤火二号”计算存储分离后,存储服务中的重要一环,全称是萤火超算文件系统(Fire-Flyer File System),

因为有三个连续的 F,因此被简称为 3FS。

Fire-Flyer 文件系统 (3FS) - 可利用现代 SSD 和 RDMA 网络的全部带宽。特点如下:

⚡  180 节点集群中的 6.6 TiB/s 聚合读取吞吐量 ⚡  在 25 节点集群中,GraySort 基准测试的吞吐量为 3.66 TiB/min ⚡  每个客户端节点 40+ GiB/s 峰值吞吐量,用于 KVCache 查找

1

关闭Page Cache:避免缓存污染,直接通过O_DIRECT + io_uring读取数据,减少内存拷贝

3fs在AI训练场景下直接关闭了文件缓存(page cache),完全依赖direct I/O模式

放弃缓存竟成杀手锏?3FS元数据系统背后的黑科技解析!

在 AI 训练场景中,3FS 之所以直接关闭文件缓存(PageCache),完全依赖 Direct I/O + 异步 I/O,主要是因为 AI 训练的数据访问模式与普通文件系统有本质不同。


✅ 为什么 3FS 抛弃 PageCache?

训练样本呈现大量随机读取:每个 batch 中的数据通常不按顺序,也不重复,读一次即弃用,

PageCache 缓存命中率极低


✅ 为什么使用 Direct I/O + 异步 I/O(AIO / io_uring)?

从磁盘直接读取到应用缓冲区,完全绕过 PageCache,避免缓存污染和系统资源浪费;

异步 I/O 提升吞吐与并发:提交多个请求无需等待,Linux 的 AIO + io_uring 组合提升了并发读性能

4.4 Ceph

高性能存储:io_uring 处理本地磁盘 I/O + RDMA 网络传输(如 Ceph 集群)

疑问:rdma 和 spdk 区别

Where Does SPDK Fit in the NVMe-oF Landscape? https://www.youtube.com/watch?v=OTVUWG-mHXU&ab_channel=SNIAVideo

RDMA:实现跨节点内存间高效、零拷贝、高并发的数据交换;

SPDK:实现本地 NVMe SSD 的用户态读写、低延迟、高 IO 性能;

两者结合:构建 NVMe-oF over RDMA,实现真正的端到端高性能、低延迟的分布式存储方案

https://www.cnblogs.com/bandaoyu/p/16752359.html。

项目

默认路径

SPDK 可选集成

I/O 驱动

内核 NVMe + BlueStore

用户态 SPDK 驱动

配置简易度

高,适合大多数用户

复杂,需额外参数

性能提升

已优化,满足多数场景

额外 +20–50%(硬件受限)

社区支持

广泛、稳定

实验性,需大调优

部署风险

低,兼容性好

高,需专业运维支持

https://www.hikunpeng.com/doc_center/source/zh/kunpengcpfs/twp/kunpengcpfs_19_0048.html

高性能云盘优化用于提升虚拟机访问云盘的速度,采用SPDK+Ceph方案,通过SPDK的控制器管理Ceph RBD设备,

并通过SPDK的轮询机制转发处理虚拟机下发的IO请求,实现相比于QEMU直接调用Ceph librbd接口更高的性能

硬件厂商:Mellanox(现 Nvidia)、Broadcom 等提供 RNIC

软件栈:

RDMA Verbs API:最底层接口,C API

libibverbs、rdma-core:中间层库

SPDK/NVMe-oF:与存储系统结合

DPDK + RDMA:纯用户态网络栈

RDMA in Kubernetes:支持 SR-IOV、RDMA Device Plugin

场景

说明

高性能存储系统(如 Ceph、NVMe-oF)

RDMA 替代 TCP,大幅提升 IOPS

分布式数据库(如 Oracle RAC、TIDB)

实现低延迟跨节点通信

高频交易系统

纳秒级延迟是关键优势

HPC 高性能计算集群

MPI 通信加速

RDMA需要专门的RDMA网卡或者InfiniBand卡才能使用,学习RDMA而又没有这些硬件设备,可以使用一个软件RDMA模拟环境,softiwarp

总结:

图片
图片

参考文章

https://zhuanlan.zhihu.com/p/258513662

https://yangtze736.github.io/%E6%8A%80%E6%9C%AF/2021/07/09/zero-copy/

https://xie.infoq.cn/article/9c9a98582c9dfd703b12d937b

https://xiaolincoding.com/os/8_network_system/zero_copy.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-27,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 一. 系统设计:Web服务器 如何实现大文件跨节点传输
    • 1.1 先排除下面几个干扰
    • 1.2 听到这个问题第一感受是什么?
    • 1.3 你必须绘制流程图出来
    • 1.4 主要矛盾
    • 1.5 自己绘制图的,自己了解一些概念(可选)
      • 谁参与系统调用?  CPU
      • 什么是 DMA控制器
      • 1.5 DMA,TCP,RDMA区别
  • 二、 解决方案:零拷贝Zero-Copy技术
    • mmap方式
    • 方式2 sendfile方式
    • endfile+DMA收集
    • splice方式
    • 三、讨论 既然零拷贝这么厉害,大文件传输那就用他了?
      • 3.1 先思考一个问题: 为什么 AIO 绕过 PageCache?
  • 四、  不同产品对比
    • 4.1 Nginx性能调优与零拷贝
    • 4.2  RocketMQ为什么使用mmap而不适应sendfile?
      • 🎯 结论先行:
      • ✅ 1. mmap 支持按需、可控的随机读写
    • 🚫 2. sendfile 只适合整块文件搬运,不适合灵活的消息系统
    • ⚖️ 3. 对比小结
      • 🧠 补充:RocketMQ 的消息读写路径设计(简化)
      • ✅ 总结一句话:
    • 4.3 3FS(DeepSeek自研文件系统)
      • 4.4 Ceph
  • 参考文章
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档