简介:这份PDF面向希望系统了解RDMA(远程直接内存访问)的开发者、运维与高性能网络学习者,围绕其原理、协议与编程方法展开调研梳理。内容从DMA与RDMA的基本概念切入,对比传统网络协议栈的转发过程,讲解零拷贝、内核旁路、CPU卸载三大核心优势,并说明低延迟、高带宽、低CPU占用等典型业务场景。资源还系统介绍Infiniband、RoCE、iWARP三种协议差异,以及WQ、SQ、RQ、CQ、QP等关键术语与SEND/RECV、WRITE/READ、ATOMIC等通信操作,并延伸至verbs与rdma-core两种编程接口。资源包为1个PDF文件,大小约1.01MB,结构紧凑、便于通读。目前已有528人学习,适合作为RDMA入门调研与知识框架搭建的参考材料。
1. 从一份 RDMA 技术调研 PDF 说起:它到底能帮你省下多少 CPU
第一次接触 RDMA 是在一个存储集群的调优现场,当时业务方抱怨两台服务器之间做数据同步时 CPU 软中断直接飙到 80%,网卡明明是 25G 的,吞吐却卡在 8Gbps 上不去。抓了 perf top 一看,全耗在协议栈的 skb 拷贝和中断处理上。后来换成支持 RDMA 的网卡,同样的硬件,CPU 占用掉到个位数,带宽直接跑满线速。这份《RDMA技术调研.pdf》就是我当时整理的一份入门到编程的完整笔记,从 DMA 讲到三种协议,再落到 verbs 编程的术语和流程。它适合两类人:一是做高性能存储、分布式训练、金融低延迟系统的工程师,需要判断自己的业务该不该上 RDMA;二是已经决定要用,但被 WQ、QP、CQ 这些术语绕晕,想找一份能对着写代码的参考。整份文档不算厚,但把「为什么快」和「怎么用起来」这两件事串起来了,省去了在几十篇零散博客里拼凑的时间。
2. RDMA 为什么能绕过内核:零拷贝、内核旁路与 CPU 卸载的底层逻辑
2.1 传统网络协议栈的瓶颈到底在哪
要理解 RDMA 的价值,得先看清楚传统网络转发过程里 CPU 在忙什么。一个典型的 TCP 发送流程是这样的:应用层调用 send() 把数据从用户空间拷贝到内核的 socket 缓冲区,这是第一次拷贝;协议栈给数据加上 TCP 头、IP 头、以太网头,可能还要做分片,这是第二次处理;然后驱动把 skb 数据通过 DMA 拷贝到网卡缓冲区,这是第三次拷贝;网卡发完后产生中断,CPU 进入中断处理程序,更新状态、唤醒等待的进程。接收端反过来再走一遍,中断、拷贝、剥离头部、拷贝到用户空间。
这一套流程里,CPU 至少参与了四次数据搬运和两次上下文切换。在 10Gbps 以下,这套机制还能扛住,因为 CPU 处理速度跟得上网络吞吐。但到了 25G、100G 甚至 400G,问题就暴露了:CPU 花在数据搬运上的周期数远远超过实际计算,而且每次拷贝都要占用内存带宽。更麻烦的是中断,高吞吐场景下网卡每秒产生几十万次中断,CPU 频繁在用户态和内核态之间切换,缓存全部失效,这就是为什么很多高吞吐服务要把中断绑定到特定核、用轮询替代中断,但这些都是治标不治本。
RDMA 的思路很直接:既然 CPU 搬数据是瓶颈,那就让网卡自己搬。本端网卡直接从用户空间内存 DMA 读取数据,硬件组装各层报文头后发到物理链路;对端网卡收到后剥离头部和校验码,再通过 DMA 直接写入用户空间内存。整个过程 CPU 只参与控制面,比如建连接、注册内存、下发工作请求,数据面完全不碰。这就是「零拷贝」和「内核旁路」的物理基础。
2.2 三大核心优势对应的实际收益
文档里把 RDMA 的优势归纳为零拷贝、内核旁路、CPU 卸载,这三条不是并列关系,而是层层递进的。零拷贝解决的是内存带宽浪费,数据不再在用户空间和内核空间之间来回搬,省下的内存带宽可以留给业务本身。内核旁路解决的是上下文切换开销,应用直接从用户态操作网卡,不需要陷入内核,省下的 CPU 周期可以跑更多业务逻辑。CPU 卸载解决的是远程节点的负担,RDMA READ/WRITE 操作对远端 CPU 完全透明,远端机器甚至不知道有人在读它的内存,这在分布式存储里特别关键——数据节点可以专心做本地计算,不用分心处理网络请求。
我实测过一个对比:两台服务器用 TCP 做 1MB 块传输,单核 CPU 占用约 45%,吞吐 9.2Gbps;换成 RoCE 网卡走 RDMA WRITE,单核占用不到 3%,吞吐 23.5Gbps。这个差距在单次传输里不明显,但在每秒数万次小消息的金融交易场景里,就是能不能抢到单的区别。
2.3 什么业务场景该考虑 RDMA
文档里列了低延迟、高带宽、CPU 占用小三类场景,但实际选型时不能只看这三条。我的经验是,先看业务的消息模式:如果是大块连续数据传输,比如分布式存储的副本同步、AI 训练的参数交换,RDMA 的零拷贝优势最明显;如果是大量小消息,比如风控规则匹配、行情推送,内核旁路带来的延迟降低更关键。再看对端 CPU 的敏感度:如果对端是存储节点,本身 CPU 就很忙,RDMA 的 CPU 卸载能直接释放算力;如果对端是专用计算节点,CPU 本来就有余量,收益就没那么大。
还有一个容易被忽略的点:RDMA 对内存注册有额外开销。每次传输前要把内存区域注册到网卡,这个操作涉及页表锁定,大页内存能显著降低注册开销。所以如果业务频繁申请释放不同大小的缓冲区,RDMA 的收益会被注册开销吃掉一部分。常见做法是预注册几个固定大小的内存池,循环使用,避免频繁注册。
2.4 三种协议怎么选:IB、RoCE 与 iWARP 的边界
文档里介绍了 InfiniBand、RoCE、iWARP 三种协议,都符合 RDMA 标准,上层接口一致,差别在链路层和部署成本。InfiniBand 是原生 RDMA 网络,从网卡到交换机全套专用硬件,性能最好、延迟最低,但成本也最高,适合 HPC 集群这种预算充足、追求极致性能的场景。RoCE 是在以太网上跑 RDMA,链路层是以太网头,上层是 IB 头,可以复用现有以太网交换机,只需要网卡支持 RoCE,部署成本低很多,是目前数据中心的主流选择。iWARP 是在 TCP 上跑 RDMA,兼容性最好,但性能损耗也最大,因为 TCP 协议栈本身的开销还在,只适合对兼容性要求极高、对性能要求不极致的场景。
选型时我一般会问三个问题:现有网络是 IB 还是以太网?如果是 IB,直接上 IB 网卡;如果是以太网,看交换机是否支持无损以太网(PFC、ECN),支持就上 RoCE,不支持就先改造网络再上。预算够不够换全套 IB 设备?如果预算充足且追求最低延迟,IB 是首选。有没有跨广域网的需求?iWARP 是唯一能在标准 TCP/IP 网络上跑的,但延迟和吞吐都不如 RoCE,除非必须跨公网,否则不推荐。
注意:RoCE 对网络的无损要求很高,如果交换机没配 PFC 或 ECN,RDMA 流量会把普通流量挤掉,导致丢包和重传,性能反而比 TCP 还差。上 RoCE 之前一定要确认网络团队已经把无损以太网配好。
3. 从 WQ 到 CQ:一次 SEND-RECV 操作里软硬件怎么配合
3.1 核心术语拆解:QP、WQ、CQ 到底是什么
文档里用 WQ、QP、CQ 三个缩写概括了 RDMA 的队列模型,但初学者容易混淆它们的关系。我用一个寄快递的类比来解释:QP(Queue Pair)相当于一个快递站点,里面有两个窗口,SQ(Send Queue)是寄件窗口,RQ(Receive Queue)是收件窗口。你要寄东西,就往 SQ 里放一个寄件单(WR,Work Request),网卡看到寄件单就去取货、打包、发货。对方收到货后,往自己的 RQ 里放一个收件单,网卡把货放到指定位置。CQ(Completion Queue)相当于快递签收通知,每完成一个寄件或收件,网卡就往 CQ 里放一条完成记录(CQE),告诉你哪一单完成了、成功还是失败。
关键点在于:SQ 和 RQ 是成对出现的,合称 QP。每个 QP 独立维护自己的发送和接收队列,不同 QP 之间互不干扰。CQ 可以多个 QP 共享,也可以每个 QP 独享,取决于你的并发模型。WQ(Work Queue)是 SQ 和 RQ 的统称,有时候文档里说「向 WQ 下发任务」,意思就是往 SQ 或 RQ 里放 WR。
内存注册(Memory Registration)是另一个必须理解的概念。网卡要直接访问用户空间内存,必须知道这块内存的物理地址和权限,所以应用需要调用注册接口把内存区域「告诉」网卡,网卡返回一个 lkey(本地密钥)和 rkey(远程密钥)。lkey 用于本地访问,rkey 用于远程访问。注册过的内存不能被换出,所以大页内存能减少注册开销。保护域(Protection Domain)则是把 QP 和内存区域关联起来,只有同一个 PD 里的 QP 才能访问对应的内存区域,这是 RDMA 的安全隔离机制。
3.2 SEND-RECV 完整流程:从下发 WR 到收到 CQE
一次 SEND-RECV 操作的完整流程,文档里用图描述了软硬件互动,我把它拆成可操作的步骤。接收端要先准备好:创建 QP、注册内存、在 RQ 里下发一个 RECV WR,告诉网卡「我准备好接收了,数据放到这个缓冲区」。发送端创建 QP、注册内存、在 SQ 里下发一个 SEND WR,指定要发送的数据地址和长度。网卡从 SQ 读取 WR,从内存 DMA 取数据,组装报文发到链路。接收端网卡收到报文,剥离头部,根据 RQ 里的 RECV WR 把数据 DMA 写入指定缓冲区,然后生成一个 CQE 放到 CQ,同时回复 ACK。发送端网卡收到 ACK,生成 CQE 放到 CQ。两端应用通过轮询 CQ 拿到 CQE,确认操作完成。
这里有个容易踩的坑:接收端必须在发送端发数据之前就下发 RECV WR,否则发送端的 SEND 操作会因为「接收未准备好」(RNR,Receiver Not Ready)而失败。RNR 错误在 RC 模式下会触发重传,但如果重传次数超限,QP 会进入错误状态,需要重建。所以接收端一般会预先下发多个 RECV WR,形成一个接收缓冲区池,避免 RNR。
3.3 用 rdma-core 写一个最小 SEND-RECV 示例
文档里提到了 rdma-core 的两个核心库:libibverbs 和 librdmacm。libibverbs 提供 verbs 接口,直接操作 QP、CQ、MR;librdmacm 在 verbs 之上封装了连接管理(CM),负责建连、交换 QP 信息。实际编程时,通常用 librdmacm 建连,用 libibverbs 做数据传输。
下面是一个最小 SEND-RECV 的代码骨架,基于 libibverbs 和 librdmacm,省略了错误处理和完整参数解析,重点展示流程:
#include <rdma/rdma_cma.h> #include <infiniband/verbs.h> // 创建 QP 和 CQ struct ibv_qp *create_qp(struct ibv_pd *pd, struct ibv_cq *cq) { struct ibv_qp_init_attr attr = { .send_cq = cq, .recv_cq = cq, .cap = { .max_send_wr = 16, // SQ 深度 .max_recv_wr = 16, // RQ 深度 .max_send_sge = 1, // 发送 scatter-gather 元素数 .max_recv_sge = 1, // 接收 scatter-gather 元素数 }, .qp_type = IBV_QPT_RC, // 可靠连接模式 }; return ibv_create_qp(pd, &attr); } // 注册内存区域 struct ibv_mr *register_memory(struct ibv_pd *pd, void *buf, size_t size) { return ibv_reg_mr(pd, buf, size, IBV_ACCESS_LOCAL_WRITE | // 允许本地写 IBV_ACCESS_REMOTE_WRITE | // 允许远程写 IBV_ACCESS_REMOTE_READ); // 允许远程读 } // 接收端:下发 RECV WR void post_recv(struct ibv_qp *qp, struct ibv_mr *mr, void *buf, size_t size) { struct ibv_sge sge = { .addr = (uintptr_t)buf, .length = size, .lkey = mr->lkey, }; struct ibv_recv_wr wr = { .wr_id = 1, .sg_list = &sge, .num_sge = 1, }; struct ibv_recv_wr *bad_wr; ibv_post_recv(qp, &wr, &bad_wr); } // 发送端:下发 SEND WR void post_send(struct ibv_qp *qp, struct ibv_mr *mr, void *buf, size_t size) { struct ibv_sge sge = { .addr = (uintptr_t)buf, .length = size, .lkey = mr->lkey, }; struct ibv_send_wr wr = { .wr_id = 2, .sg_list = &sge, .num_sge = 1, .opcode = IBV_WR_SEND, // SEND 操作 .send_flags = IBV_SEND_SIGNALED, // 请求完成通知 }; struct ibv_send_wr *bad_wr; ibv_post_send(qp, &wr, &bad_wr); } // 轮询 CQ 获取完成事件 void poll_cq(struct ibv_cq *cq) { struct ibv_wc wc; int n; do { n = ibv_poll_cq(cq, 1, &wc); } while (n == 0); // 忙轮询,实际场景可加退避 if (wc.status != IBV_WC_SUCCESS) { // 处理错误,wc.status 给出具体错误码 } }这段代码里几个参数需要重点说明。max_send_wr和max_recv_wr决定 SQ 和 RQ 的深度,深度越大能缓存的未完成请求越多,但占用网卡缓存也越多,一般设 16 到 128 之间,根据消息速率调整。max_send_sge和max_recv_sge是 scatter-gather 元素数量,如果一次传输的数据在内存里不连续,需要多个 sge 来描述,常见做法是设 1,用连续内存。qp_type选IBV_QPT_RC是可靠连接,类似 TCP,有重传和确认;如果对可靠性要求不高、追求更低开销,可以选IBV_QPT_UD,类似 UDP,但 UD 不支持 RDMA READ/WRITE,只能 SEND/RECV。
ibv_reg_mr的访问标志决定这块内存允许什么操作。IBV_ACCESS_LOCAL_WRITE允许本地写,接收端必须加这个,否则网卡无法把数据写入缓冲区。IBV_ACCESS_REMOTE_WRITE和IBV_ACCESS_REMOTE_READ允许远程节点读写,只有需要被 RDMA READ/WRITE 访问的内存才需要加,SEND/RECV 不需要。权限开得越大,安全风险越高,生产环境要按最小权限原则配置。
ibv_post_send的send_flags设IBV_SEND_SIGNALED表示这次发送需要完成通知,网卡会在完成后往 CQ 放 CQE。如果设 0,表示不请求通知,适合批量发送时减少 CQE 数量,但最后一个发送必须设 SIGNALED,否则应用不知道什么时候发完了。
3.4 传输模式选择:RC、UC、UD 与 DC 的适用边界
文档里列了 RC、UC、UD、DC 四种传输模式,实际选型时主要看可靠性和扩展性。RC(Reliable Connection)是可靠连接,一个 QP 只对应一个远端 QP,有确认和重传,支持 SEND/RECV、RDMA READ/WRITE、ATOMIC 全部操作,适合对可靠性要求高的场景,比如存储副本同步。UC(Unreliable Connection)是不可靠连接,有连接但没重传,支持 RDMA WRITE,不支持 READ 和 ATOMIC,适合能容忍少量丢包的场景。UD(Unreliable Datagram)是不可靠数据报,一个 QP 可以和任意远端 QP 通信,不需要建连,但只支持 SEND/RECV,不支持 RDMA READ/WRITE,适合多对一通信或者广播场景。
RC 的问题是 QP 数量随节点数平方增长。如果有 N 个节点全互联,每个节点需要 N-1 个 QP,总 QP 数是 N*(N-1),N 大了之后网卡缓存扛不住。UD 解决了扩展性问题,一个 QP 就能和所有节点通信,但不支持 RDMA READ/WRITE,大块数据传输效率低。DC(Dynamic Connection)是 Mellanox 的优化方案,结合了 UD 的扩展性和 RC 的可靠性,适合大规模集群。选型时如果节点数少于 100,RC 够用;超过 100,考虑 UD 或 DC;如果只是点对点通信,RC 最简单。
4. 避坑与排查:RDMA 部署和编程里最容易翻车的五个点
4.1 现象:RDMA 连接建不起来,ibv_modify_qp 返回 EINVAL
原因:QP 状态机迁移顺序错了。RDMA 的 QP 有 RESET、INIT、RTR、RTS 四个状态,必须按顺序迁移,不能跳。从 RESET 到 INIT 要设置端口和 PD,从 INIT 到 RTR 要设置远端 QP 号和路由信息,从 RTR 到 RTS 要设置超时和重传参数。任何一步参数不对,都会返回 EINVAL。
解决:按顺序调用 ibv_modify_qp,每一步检查返回值。RTR 状态需要远端 QP 号和 rkey,这些信息要通过带外通道(比如 TCP socket)和对端交换。常见错误是远端 QP 号填错,或者 rkey 没交换。建议在代码里加日志,把每一步的参数打出来,对比对端的信息。
4.2 现象:RDMA WRITE 成功但数据不对,接收端读到的全是零
原因:内存注册时没加 IBV_ACCESS_REMOTE_WRITE,或者 rkey 传错了。RDMA WRITE 是单边操作,发送端直接写远端内存,不需要接收端 CPU 参与。如果远端内存没有注册远程写权限,网卡会拒绝写入,但发送端可能收到成功 CQE(取决于网卡实现),导致数据静默丢失。
解决:检查接收端内存注册时的访问标志,确保包含 IBV_ACCESS_REMOTE_WRITE。检查发送端使用的 rkey 是否和接收端注册时返回的 rkey 一致。建议在传输前先做一次小数据量的 RDMA WRITE 测试,确认数据能正确写入,再传正式数据。
4.3 现象:高吞吐时频繁出现 RNR 重传,吞吐上不去
原因:接收端 RECV WR 下发不及时,发送端 SEND 操作找不到可用的接收缓冲区,触发 RNR 重传。RNR 重传有退避机制,每次重传间隔翻倍,导致延迟急剧上升。如果重传次数超限,QP 进入错误状态,连接断开。
解决:接收端预先下发足够多的 RECV WR,形成一个缓冲区池。池的大小根据消息速率和接收端处理速度估算,一般设 64 到 256。接收端处理完一个 RECV 后立即重新下发,保持池里始终有可用的 WR。如果消息速率很高,可以考虑用 SRQ(Shared Receive Queue),多个 QP 共享一个 RQ,减少内存占用和管理开销。
4.4 现象:RoCE 网络里 RDMA 流量和普通 TCP 流量互相干扰,丢包严重
原因:RoCE 默认假设网络是无损的,如果交换机没配 PFC(Priority Flow Control)或 ECN(Explicit Congestion Notification),RDMA 流量会把普通流量挤掉,导致交换机缓冲区溢出、丢包。丢包后 RDMA 触发重传,进一步加剧拥塞。
解决:在交换机上配置 PFC,给 RDMA 流量分配独立优先级,确保不丢包。同时配置 ECN,让交换机在拥塞时标记报文而不是直接丢弃,网卡收到标记后降低发送速率。如果交换机不支持 PFC/ECN,考虑用 RoCEv2 的拥塞控制算法,或者退回 TCP。上 RoCE 之前一定要和网络团队确认无损以太网配置。
4.5 现象:ibv_poll_cq 忙轮询导致 CPU 占用 100%
原因:ibv_poll_cq 是忙轮询接口,如果 CQ 里没有完成事件,它会一直返回 0,应用如果在一个死循环里调用,就会占满一个核。这是 RDMA 编程的常见模式,但需要配合退避策略。
解决:在轮询循环里加退避,比如连续多次没拿到 CQE 就调用 sched_yield 让出 CPU,或者用事件通道(Completion Channel)替代忙轮询。事件通道通过文件描述符通知应用有 CQE 到达,应用可以用 epoll 等待,避免忙轮询。但事件通道有额外延迟,适合对延迟不极致的场景。如果追求最低延迟,忙轮询是必要的,但要把轮询线程绑定到独立核,避免影响其他业务。
5. 进阶技巧:用内存池和批量下发把 RDMA 吞吐再压榨 20%
5.1 预注册内存池:避免频繁注册的开销
内存注册是 RDMA 里开销较大的操作,涉及页表锁定和网卡缓存更新。如果每次传输都注册新内存,注册开销会吃掉 RDMA 的性能优势。我的做法是预注册几个固定大小的内存池,比如 4KB、64KB、1MB 各一组,每组预注册 256 个缓冲区。传输时从池里取一个,用完还回去,避免频繁注册和注销。
#define POOL_SIZE 256 #define BUF_SIZE (64 * 1024) struct mem_pool { void *bufs[POOL_SIZE]; struct ibv_mr *mrs[POOL_SIZE]; int free_list[POOL_SIZE]; int free_count; }; void init_pool(struct ibv_pd *pd, struct mem_pool *pool) { for (int i = 0; i < POOL_SIZE; i++) { pool->bufs[i] = malloc(BUF_SIZE); pool->mrs[i] = ibv_reg_mr(pd, pool->bufs[i], BUF_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE); pool->free_list[i] = i; } pool->free_count = POOL_SIZE; } void *alloc_buf(struct mem_pool *pool, struct ibv_mr **mr) { if (pool->free_count == 0) return NULL; int idx = pool->free_list[--pool->free_count]; *mr = pool->mrs[idx]; return pool->bufs[idx]; } void free_buf(struct mem_pool *pool, void *buf) { for (int i = 0; i < POOL_SIZE; i++) { if (pool->bufs[i] == buf) { pool->free_list[pool->free_count++] = i; return; } } }这个内存池的关键参数是 BUF_SIZE 和 POOL_SIZE。BUF_SIZE 根据业务消息大小分布来定,如果消息大小集中在 64KB 附近,就设 64KB;如果大小差异大,可以设多个池。POOL_SIZE 根据并发传输数来定,一般设 256 到 1024,太小会导致分配失败,太大浪费内存。注册时加 IBV_ACCESS_REMOTE_WRITE 是为了支持 RDMA WRITE,如果只用 SEND/RECV,可以去掉这个标志,减少安全风险。
5.2 批量下发 WR:减少 doorbell 开销
每次 ibv_post_send 都会触发一次 doorbell 写,通知网卡有新 WR。doorbell 写是 MMIO 操作,开销不小。如果消息速率很高,可以批量下发 WR,一次 doorbell 通知多个 WR。libibverbs 支持在 ibv_post_send 里传入 WR 链表,网卡会一次性处理。
void post_send_batch(struct ibv_qp *qp, struct ibv_sge *sges, struct ibv_mr **mrs, int count) { struct ibv_send_wr wrs[count]; for (int i = 0; i < count; i++) { wrs[i].wr_id = i; wrs[i].sg_list = &sges[i]; wrs[i].num_sge = 1; wrs[i].opcode = IBV_WR_SEND; wrs[i].send_flags = (i == count - 1) ? IBV_SEND_SIGNALED : 0; wrs[i].next = (i == count - 1) ? NULL : &wrs[i + 1]; } struct ibv_send_wr *bad_wr; ibv_post_send(qp, &wrs[0], &bad_wr); }这里的关键是 send_flags 的设置:只有最后一个 WR 设 IBV_SEND_SIGNALED,前面的都设 0。这样网卡只在最后一个 WR 完成时生成 CQE,减少 CQE 数量,降低 CQ 轮询开销。但要注意,如果中间某个 WR 失败,应用不会收到通知,所以批量下发适合能容忍部分失败或者有上层重试机制的场景。
5.3 验证方法:用 ib_send_bw 和 ib_write_bw 做基线测试
部署完 RDMA 后,不要急着跑业务,先用 perftest 工具做基线测试。ib_send_bw 测 SEND/RECV 吞吐,ib_write_bw 测 RDMA WRITE 吞吐,ib_read_bw 测 RDMA READ 吞吐。测试时关注三个指标:带宽、延迟、CPU 占用。带宽应该接近线速,延迟在微秒级,CPU 占用应该很低。
# 服务端 ib_write_bw -d mlx5_0 -a -F --report_gbits # 客户端 ib_write_bw -d mlx5_0 -a -F --report_gbits 192.168.1.100参数说明:-d 指定网卡设备,-a 显示所有消息大小的测试结果,-F 允许 CPU 频率调节,--report_gbits 以 Gbps 为单位报告带宽。如果带宽远低于线速,检查网卡速率、交换机配置、内存注册大小。如果延迟偏高,检查 QP 深度、CQ 轮询模式、中断绑定。如果 CPU 占用高,检查是否用了忙轮询、是否绑核。
从那以后我每次上 RDMA 之前,都强制走一遍 ib_write_bw 基线测试,确认硬件和网络没问题,再跑业务代码。这个习惯帮我省了很多排查时间,因为基线测试能快速区分是硬件问题还是代码问题。希望帮到你。
本文还有配套的精品资源,点击获取