RDMA 文件传输踩坑(四):从固定延时改造为 READY/ACK 协议

关键词:停等协议、READY、MARKER、FINISHED、应用层 ACK、STAR

S|Situation:解决覆盖问题后,又出现发送端与接收端的时序竞争

在排查 AOF 文件同步时,我先发现多个 Receive WR 复用同一块接收内存的问题。直觉上的修复是把接收端改成每次只提交一个 RECV。然而,单缓冲区虽然避免了并发覆盖,也带来一个新的约束:主机不能在从机消费当前消息并重新提交下一次 RECV 之前,继续发送下一条消息。特别是 AOF 文件小于单块容量时,主机通常连续发送 DATA 和结束标记 MARKER。如果没有明确同步,MARKER 仍可能在从机尚未准备好时到达。

过去的临时方案是 usleep(),但它只是在猜测对方处理完成的时间。为了让同步过程具有可解释性,我为原有 RDMA_MSG_DATA 和 RDMA_MSG_MARKER 增加了 RDMA_MSG_READY、RDMA_MSG_FINISHED、RDMA_MSG_ERROR 等控制类型。

T|Task:设计不会覆盖缓冲区、也不会提前结束的协议

协议必须同时解决两个方向的问题:从机准备好接收 DATA/MARKER 后,如何通知主机;主机准备好接收 READY/FINISHED 后,如何避免控制消息丢失。还需要区分三个状态:传输层 SEND 完成、从机已消费一个 DATA 块、从机已完成整个文件同步。若只用一个含糊的 ACK,很容易在项目中误判"发送成功就是同步完成"。

A|Action:把握手拆成每块 READY 与最终 FINISHED

我采用停等式协议,控制消息仍使用同一 RDMA 连接和 8 字节消息头:

复制代码
typedef enum {
    RDMA_MSG_DATA = 1,
    RDMA_MSG_MARKER = 2,
    RDMA_MSG_READY = 3,
    RDMA_MSG_FINISHED = 4,
    RDMA_MSG_ERROR = 5
} rdma_msg_type_t;

理想时序如下:

复制代码
主机                         从机
预先 post_recv(READY)
                             post_recv(DATA)
         <--- READY ----------
收到 READY
预先 post_recv(下次READY)
         ---- DATA ---------->
                             RECV完成并 fwrite
                             post_recv(下一条)
         <--- READY ----------
收到 READY
预先 post_recv(FINISHED)
         ---- MARKER -------->
                             校验 marker / fflush / fsync
         <--- FINISHED -------

这里有两个细节。第一,主机必须在从机发送首条 READY 之前预先提交 RECV;连接建立并不等于主机已经进入 rdma_send_file()。第二,主机在发送当前 DATA 之前,应先提交用于下一条 READY 的 RECV。这样即便从机很快处理完 DATA,也不会因为主机还没准备好控制消息接收缓冲区而发生竞争。

从机的正确顺序是:提交 RECV、发送 READY、等待 DATA、验证 wc.byte_len、写入文件、处理结束,再进入下一轮。MARKER 不计入文件数据长度;收到 MARKER 后检查内容,执行约定的文件完成操作,再发 FINISHED。对于需要崩溃后持久化语义的同步,可以在 FINISHED 前执行 fflush() 和 fsync(),但还需考虑目录项持久化、临时文件原子替换等细节。

在实现上,我还发现过一个容易遗漏的错误:从机启动函数已经提交了首个 RECV,而文件接收函数再次提交同一地址的 RECV。这说明协议设计与代码审查必须同时进行,不能只看时序图就认为实现正确。

R|Result:获得了明确的协议状态,但仍需故障验证

READY/ACK 的价值在于让主从双方根据事件而非时间推进。它使日志可以清晰表达"接收端已准备好""数据已被消费""文件已完成"。不过停等式协议牺牲了部分吞吐:每块都需要一次往返控制。工程上可以先保证正确性,再改造成多槽位 credit 协议。需要如实记录的是,上传版本在 READY 阶段还出现了 Work Request Flushed Error,所以这套设计尚不能被描述为已经完成端到端验证的最终版本。接下来仍要补齐超时、断连、重连和重复消息防护。

面试延伸:3 道八股与项目问题

1. READY 与 ACK 的语义有什么不同?

答: READY 通常是接收方对下一次发送授予许可,表示有可用接收资源;ACK 通常确认某个已经发生的事件。本文的 FINISHED 是文件级完成确认,不能与本地 SEND CQE 混为一谈。设计协议时应明确确认范围和失败后的重试语义。

2. 为什么必须提前 post 接收 READY 的 WR?

答: READY 自身也是 RDMA SEND 消息,需要对端有匹配的 Receive WR。只有数据方向准备接收缓冲区,而控制方向没有准备,会把竞争从 DATA 转移到 READY。首条控制消息最好在连接可用、对端可能发送之前就准备好。

3. 如果文件同步中途断开,如何恢复?

答: 需要会话 ID、快照版本、块序号或偏移、已确认进度和校验值。重连后先确认双方认可的最后一致位置,再从可恢复边界重传。若不能保证幂等和版本一致,直接续传可能造成重复写入或混合不同 AOF 快照。

0voice - github

相关推荐
水饺编程1 小时前
第2章:Linux 基础,制作【.tar.xz】压缩包文件
linux·c语言·汇编
阿里云基础软件1 小时前
Agent 任务提速 10.71%,内存占用降低 21.60%|Alibaba Cloud Linux 4 重大发布
java·linux·人工智能·操作系统·agent
服务器硬件项目经理1 小时前
《超节点修仙录》· 第一卷·炼气期【第04话】 超节点 vs 普通服务器——不就是多插了几张卡吗?
运维·服务器·人工智能·嵌入式硬件·深度学习·机器学习
乐维_lwops2 小时前
汽车集团网管平台建设案例:乐维网管工具实现跨区域网络统一监控与链路可视化
网络·网管平台·it监控·链路·统一监控
程序猿老A2 小时前
轻量应用服务器怎么搭建 WordPress站点?
服务器
m0_743697592 小时前
DNS的反向域名的相关问题
linux
snysy2 小时前
服务器安全加固与入侵检测实战:从SSH加固、防火墙策略到挖矿木马排查
服务器·安全·ssh
Android系统攻城狮3 小时前
Linux FFmpeg深度解析之avcodec_parameters_from_context调用流程与实战(十六)
linux·运维·ffmpeg·音视频进阶·ffmpeg音视频·车载音视频进阶
思想觉悟3 小时前
SEAndroid实战
android·linux·c++