关键词:Receive WR、RQ、RC QP、RNR、时序竞争
S|Situation:偶发成功的文件同步
在独立 RDMA 文件传输测试中,我遇到过一个很有迷惑性的现象:主机已经调用 SEND,程序也能打印"发送完成",但从机并非每次都能正常收到并处理文件。最初为了快速验证,我在发送文件头后加入 sleep(3),在每块数据后加入 usleep(100000)。测试环境负载较轻时,这种方式似乎能提高成功率,于是很容易产生"只要多等一会儿就稳定了"的错觉。然而,文件同步被整合进 KVStore 后,线程调度、文件读写和从机初始化时间都会变化,固定延时无法建立真正的同步关系。
T|Task:把时间上的猜测改成协议上的保证
需要解决的不是"等多少毫秒",而是"主机怎样知道从机已经提交了 Receive WR"。RDMA 的 SEND/RECV 与传统 Socket 的一个重要区别是:接收端必须提供可用的接收工作请求,发送的数据才能有地方落地。RC 传输具有可靠性机制,但不能把接收队列长期为空当作正常的应用层流控方案;在某些配置或设备行为下,持续的 Receiver Not Ready(RNR)会触发重试或错误。对 Soft-iWARP 环境尤其应该通过日志验证具体失败路径,而不是直接假定每次都是 RNR。
A|Action:分析错误的先后顺序
原先的逻辑近似如下:
主机:连接建立 → SEND 文件头 → sleep → SEND 数据块
从机:连接建立 → 初始化文件 → post_recv → poll_cq
问题在于,连接建立事件只证明 RDMA CM 连接流程完成,并不等于应用层已经进入文件接收循环。若主机的 SEND 先于从机的 post_recv(),应用层就依赖底层重试与时序运气。固定睡眠既可能太短,也可能浪费大量时间。例如,1600 个数据块每块休眠 100ms,单纯延时就达到 160 秒;即使服务器只需要 2ms 处理一块,也被强制等待 100ms。
我把同步条件改成"先提交接收,再明确通知对方":
从机:post_recv(DATA) → SEND READY
主机:RECV READY → SEND DATA
从机:RECV DATA → 处理完成 → post_recv(下一块) → SEND READY
这个设计还必须处理反方向的控制消息:主机要在从机发 READY 之前准备好接收 READY 的 WR。尤其是第一条 READY ,不能等主机进入耗时的文件读取流程之后才临时提交 RECV。后续排查中,我把主机首个 RECV 前移到 rdma_accept() 之前,作为避免首条控制消息竞争的一项修正建议。同时,从机只在实际接收循环中提交 DATA 的 RECV,避免启动阶段和文件接收阶段重复提交同地址的 WR。
需要注意:SEND 的本地完成并不能证明对端应用已经调用 fwrite(),也不能证明数据已经落盘。因此 READY 表示"可以接收下一条",而 FINISHED 应表示"整个文件经过检查并完成约定的写入操作",两种信号不能混为一谈。
R|Result:从概率性等待转向明确的背压机制
这次设计上的收获是把"接收端是否准备好"从一个不可观测的时间假设,变成可以记录、校验和超时处理的协议状态。当前上传代码已经体现了 READY/FINISHED 的雏形,但仍出现过 QP flushed error,说明协议设计正确不等于实现已经验证成功。实际工程中还要加入控制消息超时、断连事件监听、异常重建连接,以及重复 READY 的防御性处理。只有经过固定文件、多块文件和高负载场景验证,才能称为可靠同步。
面试延伸
1. RDMA RC 模式是否意味着应用层不需要流控?
答: 不意味着。RC 提供可靠、有序的传输语义,但接收方仍需要预先提交 Receive WR。应用层需要控制发送速率和接收缓冲区生命周期。RNR 重试是底层机制,不应代替业务层的流控和背压。
2. 为什么用 sleep(100ms) 解决网络时序问题不可靠?
答: 线程调度、I/O 延迟和系统负载不是固定常数。延时只能改变竞争发生的概率,不能证明条件成立。更好的方式是条件变量、事件通知、READY/ACK 或明确的状态机,并配合超时和错误处理。
3. 如果 READY/ACK 每块都要往返,吞吐量会怎样?
答: 停等协议吞吐量受块大小、往返时延和处理时间限制。可以先用它保证正确性,再通过多接收缓冲区、发送窗口、credit-based flow control 和批量完成事件提升吞吐。优化时必须保证每个未完成 WR 拥有独立且有效的缓冲区。
详细技术分析请查看