前几年我调一套分布式存储集群,万兆网卡,链路层跑netperf能到9.4Gbps,看着一切正常。可一换成实际的存储IO路径,CPU直接被协议栈和内存拷贝打满,业务吞吐掉到1Gbps上下,物理链路利用率连10%都不到。当时我盯着监控面板想不通:链路明明是通的,带宽明明有富余,为什么应用就是上不去?后来把问题拆到底,发现根子不在链路,而在“寻址粒度”。这个视角一旦打开,RDMA和传统以太网之间的差距就变得非常清楚了,它直接决定了你系统的性能天花板在哪一层。
先说结论:传统以太网的寻址粒度停留在“主机/网卡”这一级,数据到了网卡之后,还得靠CPU一层一层搬进应用内存;而RDMA的寻址粒度直接杀到了“内存地址”这一级,网卡硬件自己就能完成对远端内存的读写。看似只是一层寻址差异,实际导致的是整个数据通路的重构。这篇文章就按这个逻辑线展开,我会把传统以太网的开销来源、RDMA的寻址机制、性能差距的成因、以及选型时必须付出的代价挨个讲透。
1. 传统以太网的时间都被谁吃掉了:从MAC帧到应用缓冲区的漫漫长路
1.1 寻址链路的终点是“网卡的收包队列”,不是“应用内存”
传统以太网从发端到收端,寻址分三层走。第一层是MAC地址,解决“这个帧在局域网内属于哪块网卡”;第二层是IP地址,解决“这个报文在网络里该往哪个方向路由”;第三层是TCP/UDP端口,解决“这个数据该交给哪个进程”。三层叠加起来,最终定位到的其实是“某台主机上某个进程的某个socket”,也就是我们常说的四元组识别。这套寻址体系本身非常高效,互联网几十亿设备跑了几十年,靠的就是它。但问题是,它寻址的终点是“进程的socket”,而不是“进程内存里的某个具体地址”。
这意味着什么?我用自己的话说就是:数据包被网卡收下来之后,网卡只能根据四元组知道“这个包应该进哪个socket”,但socket背后对应的用户态缓冲区,网卡是不知道的。网卡能做的,只是把数据通过DMA放进内核预先分配好的一个缓冲区里,然后抬起中断告诉CPU:“有数据到了,你自己看着办。”后面的路,全部由CPU和内核来完成。
1.2 三次拷贝和中断:CPU从头到尾当搬运工
传统TCP收包路径,数据要经过三次内存拷贝和至少一次中断处理。我把它拆开写,你就能直观感受到CPU有多忙。
第一次拷贝,网卡通过DMA把数据从链路搬到内核的接收缓冲区,这一步不耗CPU,是硬件完成的。第二次拷贝,内核协议栈解析完报文头,根据四元组找到对应的socket,把数据从内核接收缓冲区拷贝到socket的接收队列。第三次拷贝,应用调用read/recv,数据从socket接收队列被拷贝到用户态的应用缓冲区。
这只是接收方向。发送方向是反过来的,应用调用write/send,CPU把数据从用户态拷贝到内核发送缓冲区,协议栈封包后在某个时刻再让网卡DMA出去。除了拷贝,还有中断。网卡每收一批包就要触发一次中断,中断处理器要保存现场、标记软中断、唤醒等待进程,然后软中断里做协议栈处理。高并发小包场景下,光处理器软中断就能吃满一个核。
我把一次TCP收发的关键开销列出来,大家感受一下:
- 用户态到内核态两次切换(syscall进入和退出)
- 内存拷贝平均两次以上(用户态到内核态、内核态到网卡)
- 一次硬中断加一次软中断处理
- 协议栈处理包括校验和计算、TCP序列号维护、窗口更新、拥塞控制状态机
- 锁竞争:多线程同时读写同一个socket时,还要在socket锁和协议栈锁上排队
1.3 谁在充当性能天花板:CPU的计算吞吐
万兆TCP能跑很高的线速,但那是在大包场景下。一旦包变小、并发变高,CPU的开销就按“包数”增长,而不是按“字节数”增长。一个1500字节的大包,和一台64字节的小包,协议栈处理开销几乎是一样的,都要经过同样的解析、拷贝、中断流程。所以当业务流量以小包为主时,CPU很快被打满,而物理链路速度还有大量富余。
这就是典型的“寻址粒度”问题:传统以太网的寻址使命在“把数据送到进程”这里就结束了,之后的每一次字节搬运都需要CPU具体执行。CPU的核数、主频、内存带宽决定了每秒能搬运多少字节,于是它就成了整条链路的“性能天花板”。你再把网卡从万兆升到25G、100G,只要数据通路还要CPU当主力搬运工,瓶颈依然在CPU上,网卡可能连一半的速率都吃不满。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RDMA的寻址另起炉灶:把“内存地址”直接交给硬件
2.1 底层思路:让网卡自己找内存,CPU只负责下发指令
RDMA的基本原理说起来很简单:把内存地址和访问权限告诉网卡硬件,让网卡自己完成数据搬运,CPU从头到尾只负责发起任务和接收完成通知。这就是大家常说的“内核旁路(kernel bypass)、零拷贝(zero copy)、CPU卸载(offload)”,其实这三件事说的都是同一个过程。
要理解RDMA的工作原理,得先忘掉TCP/IP那套“发到某个IP的某个端口”的思维。RDMA网络里,一台主机上的应用要想让远端机器上的某块内存被读写,需要提前把这资源“曝光”给远端。这个曝光过程,走的是本地用户态的verbs接口,把一块已注册内存区的一部分或全部,绑定上一个关键字(rkey)。远端发来读写请求时,只需带上这个rkey和虚拟地址,网卡硬件就能自己找到那台机器、那块内存、把数据读写进去。
2.2 一个大写的内存级寻址体系
标准一次RDMA写操作,控制信息长这样:
- 设备寻址:用GID(RoCE v2下就是IP地址加SGID索引)或LID(IB模式下)找到目标网卡端口,这个相当于传统以太网的IP和MAC层
- 队列对寻址:用QPN(Queue Pair Number)找到目标QP,相当于传统网络里的端口号
- 内存寻址:用远端内存地址(RVA)加上rkey(Remote Key),再给一个数据长度,精确到字节级地指出要写到哪里
三样东西叠加起来,寻址终点直接落到“某台机器上某块内存的某个偏移量”,并且带上了权限校验能力:rkey是注册内存时由网卡硬件生成的密钥,远端拿不到正确的rkey,就算知道地址也读写不了。这个设计粒度,比传统以太网的四元组细了好几个数量级。
我把两种寻址模式放一起对比一下:
| 寻址维度 | 传统以太网 | RDMA |
|---|---|---|
| 定位目标网络位置 | MAC + IP | GID/LID |
| 定位目标应用会话 | TCP/UDP端口(四元组) | QPN(队列对编号) |
| 定位具体数据位置 | 无(内核查socket缓冲) | RVA + rkey + length |
| 数据搬运执行者 | CPU + 内核协议栈 | 网卡硬件 |
| 权限控制粒度 | 连接级 | 内存区域级(单独授权) |
这个表格中间的差异,就是性能差异的根源。传统以太网寻址到“进程”这一层就把球踢给了CPU;RDMA在寻址阶段就自带“目标内存地址”,连rkey都一起做了身份和权限校验,硬件拿到这些信息后,不需要任何人帮忙就能完成后续全部动作。
2.3 网卡硬件里的“迷你MMU”
RDMA能做到直接读写用户态内存,底层依赖的是内存注册和地址翻译机制。应用在注册内存时,驱动会把这块内存的物理页锁定(pin住),并在网卡硬件里建立一张虚拟地址到物理地址的映射表。这张表本质上就是一个简化版的MMU,只不过它不是给CPU用的,是给网卡DMA引擎用的。
有了这张表,网卡收到远端发来的RDMA写请求后,拿着RVA查表翻译成物理地址,直接发起DMA写操作,把数据放到应用指定的用户态缓冲区里。整个过程中数据不经过内核缓冲区,不经过socket接收队列,没有内核参与,没有中间拷贝。应用甚至不需要知道数据什么时候来的,需要的时候去内存里拿就行。
这也是为什么RDMA接收端的CPU负载几乎为零,它把“找地址、搬运、校验”这些放在硬件里做完了。从寻址粒度的视角看,传统以太网是“网卡知道把包收到内核,CPU负责送到应用”;RDMA是“网卡自己就知道把数据放到应用内存的哪个位置”,后者天然省掉了整个内核数据通路。
3. 寻址粒度差一小步,性能天花板差一大截
3.1 数据通路大幅缩短
传统以太网的时延,大头不在物理链路的传播时延,而在每一跳的处理时延。本地回环的TCP ping-pong时延实测一般在20到50微秒级别,这个延迟几乎全部来自协议栈处理、中断和进程调度。RDMA回环时延我测过可以在0.5到2微秒区间,一个数量级的差距就是这么来的。
再看数据通路的长度。TCP收发数据要经过“用户态缓冲→内核发送缓冲→网卡→链路→对端网卡→对端内核接收缓冲→对端用户态缓冲”,中间的缓冲环节可以简化成“两次用户态/内核态边界切换加两次内存拷贝”。RDMA的通路是“用户态内存地址A→网卡硬件→链路→对端网卡硬件→对端内存地址B”,看起来同样是两个端点,但中间没有内核介入,没有协议栈解析,没有socket排队,没有拷贝,更没有上下文切换开销。
3.2 实测里的CPU占用和吞吐差距
我拿同一台服务器做过对比,两个工作负载都是模拟存储IO:一个走TCP socket,另一个走RDMA verbs接口。
- 小包随机读场景(4KB读):TCP路径CPU占用约80%到90%,吞吐量被CPU限制在1.5Gbps左右;RDMA路径CPU占用不到10%,吞吐能跑到接近链路线速
- 大包顺序写场景(1MB写):TCP路径CPU占用约40%,能跑到5Gbps左右;RDMA路径CPU占用不到5%,满载达到9.8Gbps
- 高并发场景(1000连接同时发送):TCP路径因锁竞争和软中断出现明显抖动,CPU满载且延迟飙高;RDMA路径靠多个QP并行,延迟曲线平稳
不同机器、不同内核版本测试结果会有出入,但趋势是一致的:只要数据通路里CPU参与得多,性能天花板就被CPU钉死了;RDMA把CPU从数据通路上摘掉,剩下的天花板就只剩网卡本身和物理链路速率。你可以把链路速度往上提,但CPU的处理能力不会因为换网卡而线性增长,这就是传统以太网在高性能领域绕不过去的墙。
3.3 为什么说“寻址粒度”是关键变量
有人可能会说,传统以太网也可以用DPDK、XDP、eBPF这些技术绕开内核,为什么说寻址粒度是天花板?我理解的重点在于:DPDK和XDP确实是绕过内核的重要手段,但它们改变的只是“数据通路上的处理者”,没有改变“寻址模式”。
用DPDK收包,应用可以自己接管网卡队列,但这块网卡队列里的包仍然需要应用程序自己按五元组做分流,自己把数据从DMA缓冲拷到最终业务内存。CPU依然在数据通路上,只是把内核协议栈的工作挪到了用户态。除非你手动实现了类似RDMA的“直接让硬件把数据写进最终地址”的机制,否则瓶颈还是会回到CPU的内存拷贝带宽上。
RDMA从一开始就把“目标内存地址”放进了寻址字段里,由硬件完成直写,于是CPU可以彻底从数据搬运中退场。所以我说,寻址粒度是这个系统设计的灵魂:它决定了数据通路上还有没有CPU,决定了你的性能天花板是“硬件线速”还是“CPU内存带宽”。
4. 高天花板的门票:RDMA落地的代价和边界条件
4.1 内存注册、固定和访问权:高性能不是白拿的
RDMA直接操作用户态内存的能力,带来一个额外要求:所有参与RDMA通信的内存区域必须先注册。注册的本质有两件事,一是把虚拟内存页固定住,不让操作系统把它换出到磁盘;二是在网卡硬件里建立地址映射表,顺便生成rkey。
这里有个工程上经常被忽略的坑。注册内存是有开销的,一次注册可能需要几十到上百微秒,如果业务里频繁注册小块内存,这个开销完全抵消掉RDMA带来的时延优势。我做过一个批量导出数据的服务,最初每处理一行数据就注册一次内存,结果性能还不如TCP。后来改成内存池策略,启动时注册一块大内存,业务期内重复复用,性能才真正起来了。如果你的业务想要用RDMA,第一个要解决的问题就是“内存复用和池化”。
另一个问题是内存固定会让页面不可换出,注册的内存越多,系统可回收内存越少。生产环境里我一般建议注册总量控制在物理内存的一定比例以内,并且监控好可用内存水位。对于无法预分配内存的场景,可以用ON-DEMAND PAGING(ODP,即按需分页)机制,让驱动在访问缺页时自动处理,但ODP会有额外的硬件缺页开销,性能会比预注册稍差。选型时要清楚这个取舍。
4.2 网络层要求:无损网络和复杂运维
RDMA虽然数据处理效率高,但对网络的容忍度很低。传统TCP有丢包重传、拥塞控制、乱序重组这些机制兜底,网络稍微拥塞一下,TCP会自动降速并重传,业务照常跑。RDMA在设计上“过于直接”,尤其RoCE这类跑在以太网上的RDMA,需要底层网络保证不丢包,一旦丢包,硬件重传成本极高,性能会断崖式下跌。
所以在实际部署RoCE时,交换机和网卡都要做不少配置:PFC(优先级流控)保证无丢包、ECN(显式拥塞通知)配合DCQCN做拥塞控制、流控阈值调优、把RDMA流量单独打上优先级并隔离存储网络等等。这些配置对运维团队的要求是实打实的,出了问题排查也比传统TCP复杂得多。这让我在项目选型时一直坚持一个原则:先问清楚自己有没有能力维护一个无损网络,再去讨论RDMA的收益。
4.3 生态和社区:RDMA并不是象牙塔里的技术
很多人听到RDMA,以为是个封闭的专用技术,实际上它的开源生态已经相当成熟。Linux内核原生就带RDMA协议栈,也就是内核的RDMA子系统,常用的驱动包括mlx5_core、qedr、efa这些。用户态的关键库和工具,由rdma-core项目提供,里面包含了ibv_verbs库、ibv_ud_pingpong测试工具、ibstat、ibv_devinfo等。OpenFabrics Alliance(OFA)是RDMA领域最核心的行业联盟,负责维护OFED(OpenFabrics Enterprise Distribution)驱动栈,很多商用网卡驱动和协议栈都由它统一发布。如果你需要高性能消息库,还可以看UCX和libfabric——前者在MPI和AI分布式训练框架里用得很多,后者主要面向存储和HPC。这些社区的代码活跃度都很高,RDMA的社区支持比你想象的完整很多,只是不如TCP生态那么“烂大街”而已。
5. 选型逻辑:反推你的业务要不要啃这块硬骨头
5.1 哪些场景能真实吃到“内存级寻址”的红利
存储网络是RDMA最典型的受益场景。NVMe over Fabrics在RDMA链路下,时延能比传统iSCSI/TCP低一个量级,队列深度高时吞吐也更稳。我做存储集群性能测试时,同样一套NVMe磁盘阵列,走TCP协议栈打满CPU时延还不稳定,切到RDMA链路之后CPU占用骤降,时延也基本平稳。AI分布式训练也是典型的RDMA大户,梯度同步的集合通信都是大块数据,用RDMA能显著缩短每次迭代的时间,尤其千卡万卡规模的训练集群,通信开销就是训练效率的天花板。HPC领域更不用说了,MPI的底层传输在高速网络上基本都是RDMA。此外还有高频交易、内存数据库同步等低时延场景,这几个地方,RDMA的硬件直访能力带来的收益远大于部署成本。
判断标准很简单:如果你的业务数据量大、需要大块连续读写远端内存,并且对延迟敏感,RDMA几乎必然能带来明显收益。
5.2 哪些场景别硬上RDMA
普通微服务之间的RPC、Web请求、短小消息的异步通信,这些场景往往连接数很多但每个连接数据量很小,用RDMA未必划算。原因有几个:一是短连接场景下,QP创建和销毁开销很高,RDMA的优势根本体现不出来;二是RDMA编程模型要求应用把内存注册好、把QP管理好,这套复杂度对普通业务来说太重了;三是如果你的服务要跨公网或者混合云部署,物理链路根本不在一个可控的无损网络里,RDMA没有用武之地。
这里我多说一句:不要一听说RDMA性能好就在所有模块上强行引入。性能优化的核心是找到真正占吞吐量的那条链路,把资源花在瓶颈上。给一个一百字节的JSON接口调用接口强行上RDMA,那是给自己找罪受。
5.3 一个相对平滑的过渡思路
如果你的系统短期内没法全面切换RDMA,可以考虑混合架构。比如计算节点到存储节点之间用传统TCP做控制面,存储节点与存储节点之间的数据复制、日志同步走RDMA;或者计算节点上先保留TCP,由存储网关或代理节点统一转成RDMA访问后端存储。NVMe over Fabrics本身也支持TCP传输,你可以先走TCP模式把框架跑起来,等网络条件成熟了再切换到RDMA传输,应用层改动不大。这个演进的路径,比一步到位把整个系统重写成RDMA版本要稳妥得多。
6. 我踩过的坑和现在的习惯
我给刚开始接触RDMA的朋友一个最实在的建议:别用TCP的思维去理解它。我前两年就是这么被坑过来的,总想着“目标IP是多少、端口是多少、要发多大buffer”,结果套到RDMA上怎么也别扭。后来我把思路反过来,把RDMA当作“网卡直接读写远端内存的DMA引擎”,而不是“网络协议”,一切就通顺了。重点不在于“发一个包过去”,而在于“把数据写到对方内存里的哪个地址”。
还有一个习惯是测试时先看“CPU是否还在数据通路上”。每次业务改造完,我会用perf看CPU在syscall、软中断、拷贝上的占比,如果占比还是很高,说明寻址粒度没有真正降下来,要么是接口没用对,要么是数据还在走内核。这个检查方法比单纯看吞吐数字实用得多,因为它直接定位了系统瓶颈到底还在不在CPU。
寻址粒度这个视角,帮我判断了很多性能架构问题的取舍。当年那台“链路有富余但应用跑不动”的存储节点,在存储网络上引入RDMA之后,CPU负载直接掉了七八成,业务吞吐才真正吃到链路带宽。如果你的系统也存在“链路带宽看着很足、业务性能就是上不去”的怪现象,不妨先用寻址粒度这把尺子量一量自己的数据通路,看看CPU是不是还待在数据搬运工的岗位上。
