协作生产运维约 14 分钟

event-bus 可靠投递

两跳转发:发件箱保证不丢,收件箱保证不重做,缺字段的毒消息单独隔离,重试用完进死信。

依据 agent-solution 0.1.3 · main@bf702bc9(2026-11-03)核实

为什么要走总线

上一关的网关默认直连运行时。直连简单,但调用方和智能体必须同时在线,一方抖动请求就失败。企业里更稳妥的做法是在中间放一条消息总线:调用方把请求交给总线就算完成,总线负责送到。

agent-solution 的 event-bus 用 RocketMQ 做消息队列、PostgreSQL 做发件箱和收件箱,请求和响应各走两跳:

调用方 ──req──▶ 中继(治理) ──deliver──▶ 运行时
调用方 ◀─resp_out── 中继(治理) ◀─resp_in── 运行时
源码event-bus READMEagent-solution/common/agent-bus/event-bus/README.md@bf702bc9

中继在两跳之间做治理:判重、租户、关联匹配、毒消息拦截。它是总线里唯一的独立进程;调用方和运行时只需引入 SDK,用开关选择自己的角色(caller / runtime / relay 三选一)。

发件箱:不丢

调用方不直接往队列里发消息,而是先写进自己库里的发件箱,再由后台领取、发送、标记。发件箱记录的状态:

状态含义
PENDING已落库,等待发送
DISPATCHING已被某个工作者领取(带租约)
ACKED发送成功(终态)
RETRY_SCHEDULED发送失败,约定下次尝试时间
DLQ重试用完,进死信(终态)
EXPIRED超过自身截止时间(终态)
源码ForwardingStatusagent-solution/common/agent-bus/event-bus/event-bus-spi/src/main/java/com/openjiuwen/bus/forwarding/spi/ForwardingStatus.java:27@bf702bc9

默认重试策略:从 100 毫秒起指数退避,单次间隔不超过 60 秒,最多 5 次,用完进 DLQ。重试时机由策略决定,传输层只负责报告"这次失败能不能重试"。

源码ForwardingRetryPolicy.DEFAULTagent-solution/common/agent-bus/event-bus/event-bus-sdk/src/main/java/com/openjiuwen/bus/forwarding/runtime/ForwardingRetryPolicy.java:63@bf702bc9

收件箱:不重做

消息队列的承诺是"至少投递一次",也就是说可能重复。收件箱按消息记账:

状态含义
RECEIVED收到了,还在处理
CONSUMED处理完成(终态)
DUPLICATE_SUPPRESSED重复投递,已抑制(终态)
REJECTED治理拒绝(终态)

中继收到消息时:

  • 有终态行 → DUPLICATE_SUPPRESSED,提交确认,不重新发布
  • 有 RECEIVED 行(上次处理到一半崩了)→ 允许再发布一次;发件箱的确定性消息 ID 保证第二跳不会发两份
  • 发件箱里这条已经 ACKED(上次其实发成功了,只是没来得及记账)→ 直接补记 CONSUMED 并提交
源码EventBusRelayWorker.runOnceagent-solution/common/agent-bus/event-bus/event-bus-relay/src/main/java/com/openjiuwen/bus/forwarding/runtime/relay/EventBusRelayWorker.java:203@bf702bc9

毒消息:隔离,不重投

消息必须把控制字段(traceId、幂等键、路由句柄、能力)作为一等字段带上。缺了的消息,中继判定为毒消息:提交确认、收件箱记 REJECTED。

为什么不让队列重投?因为缺字段是消息本身的问题,重投一万次也修不好,反而会堵住后面的正常消息。

说明

README 特别提醒:broker 集成测试在没有真实 RocketMQ 时会被跳过,默认测试全绿不等于真实环境验收通过;仓库提供了 fail-closed 的验收脚本。

动手:预测投递结果

模拟器加载中…

小结

  • 总线让调用方和智能体解耦:请求和响应各走两跳,中继在中间做治理。
  • 发件箱先落库再发送,保证不丢;重试有上限,用完进死信。
  • 收件箱按消息记账,重复投递只确认不重做。
  • 缺控制字段的毒消息直接隔离,不让它卡住队列。

源码出处

链接指向固定提交,仓库后续更新不影响这里的行号。

过关测验

  1. 1. 发件箱(outbox)解决的是什么问题?

  2. 2. 消息队列把一条已经处理完的消息又投了一次,中继会怎样?

  3. 3. 缺少控制字段的毒消息为什么不让队列重投?

通关条件

  • ○ 完成模拟器任务(0/3)
  • ○ 测验全部答对