关键词:停等协议、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 快照。