首页/开发/系统分析与架构设计速查/发件箱模式(Transactional Outbox)

发件箱模式(Transactional Outbox)

在本地事务中同时写业务数据与发件箱记录,再由独立中继把发件箱内容发布到消息系统;避免“数据库已提交但消息未发出”的不一致。

发件箱模式是什么

发件箱模式解决数据库已提交但消息没发出去的不一致:在同一个本地事务里同时写业务数据和发件箱记录,再由独立中继把发件箱内容发布到消息系统。业务侧只关心本地事务是否成功,消息投递的可靠性交给中继保证。

为什么需要它

业务代码直接调用消息中间件时,两个系统无法共享同一个事务。先提交数据库再发消息,中间崩溃就会丢消息;先发消息再提交数据库,则可能发出并不存在的业务事件。发件箱把这两步合并为一次本地原子写入,剩下的问题只有中继必须至少投递一次,因此消费端仍然要幂等。

基本信息

写入业务表与发件箱表在同一本地事务中提交
发布中继轮询或变更数据捕获读取发件箱并投递
消费端仍需幂等,投递语义通常是至少一次
可观测保留待发与已发状态、重试次数,便于补偿与排查

表结构与中继

表结构与中继
CREATE TABLE outbox (
  id BIGINT PRIMARY KEY,
  aggregate_id VARCHAR(64),
  payload JSON,
  status TINYINT,      -- 0 待发 1 已发
  retry INT DEFAULT 0,
  created_at DATETIME
);
-- 中继循环:取一批待发记录 → 投递成功 → 标记已发
-- 投递失败保留待发状态并累加重试次数,超阈值转人工处理

关键字段

字段作用
id单调递增主键,可用于排序与断点续传
aggregate_id聚合标识,便于消费端幂等与按业务排序
payload事件内容,包含事件类型与业务数据
status待发与已发状态,决定中继是否继续处理
retry重试次数,用于识别长期失败的消息
created_at创建时间,用于归档与清理策略

中继怎么选

轮询中继实现简单,用一个定时任务按主键顺序取待发记录投递即可,代价是空轮询与秒级延迟;变更数据捕获直接读取数据库日志,延迟更低、对业务表压力更小,但需要额外的组件与运维能力。数据量不大时优先轮询,追求低延迟或投递量很大时再考虑变更数据捕获。

发件箱表会持续增长,必须配套归档与清理策略;保留期应覆盖线上问题回溯与补偿所需的时间窗口。

易错点

  • 先写库再直接发消息而没有发件箱,中间故障直接丢消息
  • 发件箱表无限增长,未做归档与清理,查询与存储成本持续上升
  • 中继重复投递但消费端未做幂等,业务被重复执行
  • 发件箱写入与业务写入放在不同事务里,原子性丢失,问题依旧存在

数据来源与参考

  • microservices.io 模式目录:Transactional Outbox
  • Debezium 文档:Outbox Event Router
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌