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 | 超过自身截止时间(终态) |
默认重试策略:从 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并提交
毒消息:隔离,不重投
消息必须把控制字段(traceId、幂等键、路由句柄、能力)作为一等字段带上。缺了的消息,中继判定为毒消息:提交确认、收件箱记 REJECTED。
为什么不让队列重投?因为缺字段是消息本身的问题,重投一万次也修不好,反而会堵住后面的正常消息。
README 特别提醒:broker 集成测试在没有真实 RocketMQ 时会被跳过,默认测试全绿不等于真实环境验收通过;仓库提供了 fail-closed 的验收脚本。
动手:预测投递结果
小结
- 总线让调用方和智能体解耦:请求和响应各走两跳,中继在中间做治理。
- 发件箱先落库再发送,保证不丢;重试有上限,用完进死信。
- 收件箱按消息记账,重复投递只确认不重做。
- 缺控制字段的毒消息直接隔离,不让它卡住队列。
源码出处
- agent-solution/common/agent-bus/event-bus/README.md两跳转发、模块结构、角色开关
- agent-solution/common/agent-bus/event-bus/event-bus-spi/src/main/java/com/openjiuwen/bus/forwarding/spi/ForwardingEnvelope.java:43
public record ForwardingEnvelope(转发信封 - agent-solution/common/agent-bus/event-bus/event-bus-spi/src/main/java/com/openjiuwen/bus/forwarding/spi/ForwardingStatus.java:27
public enum Outbox发件箱与收件箱的状态 - agent-solution/common/agent-bus/event-bus/event-bus-relay/src/main/java/com/openjiuwen/bus/forwarding/runtime/relay/EventBusRelayWorker.java:203
public RelayTickResult runOnce(中继:判重、毒消息、崩溃恢复 - agent-solution/common/agent-bus/event-bus/event-bus-sdk/src/main/java/com/openjiuwen/bus/forwarding/runtime/ForwardingRetryPolicy.java:63
ForwardingRetryPolicy DEFAULT默认指数退避与次数上限
链接指向固定提交,仓库后续更新不影响这里的行号。
过关测验
1. 发件箱(outbox)解决的是什么问题?
2. 消息队列把一条已经处理完的消息又投了一次,中继会怎样?
3. 缺少控制字段的毒消息为什么不让队列重投?