bigywater.tech
RabbitMQ · 29

RPC 模式:reply_to 与 correlation_id

用消息队列做 RPC:请求怎么告诉服务端“把结果发到哪”,多个请求并发时怎么把响应对回去?回复队列怎么建才不拖慢?服务端挂了怎么办?这篇逐项在线上和延迟上量一遍。

3.317 vs 0.237 ms
每次新建回复队列与 direct reply-to 的 p50 往返延迟
req-1 最后到
快慢两个服务端并发时,响应到达顺序与请求顺序不同,靠 correlation_id 对应
2 条
无服务端、一个带 expiration 的请求被队头挡住,3.5 秒后 rpc_queue 里剩的条数

基本机制

实测 · 本机执行输出
  客户端声明了独占队列 amq.gen-…(服务器生成的名字,前缀 amq.gen-);请求 correlation_id = 1e550c17…;收到响应 'resp:ping',correlation_id 是否一致: True;往返 0.5 ms
  C→S  ch1  Basic.Publish   26 字节
  C→S  ch1  ContentHeader   90 字节
  S→C  ch1  Basic.Deliver   92 字节
  S→C  ch1  ContentHeader   59 字节

请求带 reply_to(回复队列名)和 correlation_id(唯一标识)。服务端把响应发到默认 exchange、routing key=reply_to,并带回同一个 correlation_id。

乱序

实测 · 本机执行输出
  请求发送顺序: req-0 .. req-5;响应到达顺序: ['req-0', 'req-2', 'req-3', 'req-4', 'req-5', 'req-1'];每个响应的 correlation_id 都能对回自己的请求: True

多个请求并发时响应可能乱序到达(服务端实例的速度不同),只有 correlation_id 能把响应对回请求。

回复队列的做法

实测 · 本机执行输出
  (a) 每次新建独占队列       p50 3.317 ms,p99 5.261 ms,292 次/秒
  (b) 复用独占回复队列       p50 0.257 ms,p99 0.459 ms,3738 次/秒
  (c) direct reply-to p50 0.237 ms,p99 0.369 ms,4043 次/秒

为每个请求新建回复队列最慢(p50 3.317 ms),复用一个回复队列(0.257 ms)和 direct reply-to(0.237 ms)快一个数量级。

服务端不在

实测 · 本机执行输出
  没有服务端,客户端等了 3 秒,收到回复: 0 条
  发了两个请求:一个没有 expiration、一个 expiration=1000 ms。3.5 秒后 rpc_queue 里剩 2 条(没有 expiration 的那条当然还在;带 expiration 的那条排在它后面,出不了队头,也没有被丢掉——这就是第 11 篇的队头阻塞)

RPC 没有天然的超时,客户端要自己设;请求的 expiration 受队头阻塞影响,不是一个可靠的清理手段。

分步演示

RPC 模式
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / RabbitMQ 4.3.6 / Erlang/OTP 28.5 / pika 1.4.4(Homebrew 与临时虚拟环境);所有节点都是本机自己起的 RabbitMQ 进程(127.0.0.1,端口 9500 以上,每个实验独立的数据目录与节点名,不连任何外部服务);命令输出来自真实的 rabbitmqctl 与管理 API,发布与消费用 pika 的真实连接;计时用 time.perf_counter,数字每次运行会有小幅抖动。线上帧经自己写的 TCP 代理;往返延迟是 2000 次顺序调用(服务端立即回复),pika BlockingConnection;乱序实验用两个消费 rpc_queue 的服务端线程(慢 300 ms、快 10 ms,prefetch=1),响应顺序受调度影响,每次运行可能不同
    • 没有验证:direct reply-to 在服务端和客户端不同连接时的限制(必须同一 channel 上订阅);quorum 队列作为 rpc_queue;并发请求数很大时回复队列的吞吐;服务端崩溃时未回复请求的重投与重复响应;客户端的超时与重试策略的完整实现
    京ICP备2026067502号-1京公网安备11010502063487号