第 7 / 21 篇

第 6 章:消息成功送达,追踪为什么仍会断开

原创示意:消息上下文正例与断点

同步 HTTP 调用很容易让人把上下文传播理解成“调用栈里的变量”。消息跨过队列后,生产者与消费者可能相隔很久,也可能运行在不同进程。消费者线程不会因为业务上处理了同一条消息,就自动拥有生产者的 Context。

6.1 先区分三种身份

订单号标识业务对象,消息 ID 标识一次消息投递相关的技术对象,trace ID 标识一次因果执行。它们不应互相替代。一个订单可能产生创建、支付、取消等事件,一个事件也可能被重投;如果只靠订单号聚合执行,就无法区分独立读取和重试。

实验在 producer span 成为当前上下文后,把 W3C 字段注入 RocketMQ message user properties。消费者从 MessageExt 取回这些字段,以提取结果创建 CONSUMER span。应用 JSON 保留订单号与受控消费等待参数,消息协议字段保留技术上下文,两者职责分开。

验收要求 consumer 的父 span ID 等于 producer 的 span ID,并且三个服务的 span 出现在同一个 trace 中。只检查 trace ID 相等还不够,因为一个错误的上下文选择也可能保留 ID,却把工作挂到错误的父节点。

6.2 一个刻意保留的负例

向 /orders 提交:

{"id":"broken-context","propagate":false}

订单依然写入 Redis,消息依然被 RocketMQ 消费,消费者依然写入结果。差异是消费者没有可提取的技术上下文,于是创建新的根 trace。验收通过 worker.processed 事件取得第二个 trace ID,再分别查询两个 trace,证明业务成功与追踪连续性是两个独立属性。

这个负例在真实系统里很常见:某条旧消息格式没有属性透传,转发服务只复制正文,或者框架更换了属性名。排查时若只看“消息没有积压”,会完全错过可观测性断点。反之,看到断点也不能立即断言消息丢失。