发件箱模式是什么
发件箱模式解决数据库已提交但消息没发出去的不一致:在同一个本地事务里同时写业务数据和发件箱记录,再由独立中继把发件箱内容发布到消息系统。业务侧只关心本地事务是否成功,消息投递的可靠性交给中继保证。
为什么需要它
业务代码直接调用消息中间件时,两个系统无法共享同一个事务。先提交数据库再发消息,中间崩溃就会丢消息;先发消息再提交数据库,则可能发出并不存在的业务事件。发件箱把这两步合并为一次本地原子写入,剩下的问题只有中继必须至少投递一次,因此消费端仍然要幂等。
基本信息
| 写入 | 业务表与发件箱表在同一本地事务中提交 |
|---|---|
| 发布 | 中继轮询或变更数据捕获读取发件箱并投递 |
| 消费端 | 仍需幂等,投递语义通常是至少一次 |
| 可观测 | 保留待发与已发状态、重试次数,便于补偿与排查 |
表结构与中继
表结构与中继
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 | 创建时间,用于归档与清理策略 |
中继怎么选
轮询中继实现简单,用一个定时任务按主键顺序取待发记录投递即可,代价是空轮询与秒级延迟;变更数据捕获直接读取数据库日志,延迟更低、对业务表压力更小,但需要额外的组件与运维能力。数据量不大时优先轮询,追求低延迟或投递量很大时再考虑变更数据捕获。
发件箱表会持续增长,必须配套归档与清理策略;保留期应覆盖线上问题回溯与补偿所需的时间窗口。