Transaction Boundary、Outbox 与 Idempotency
保存订单成功后,发送消息失败了怎么办?数据库与消息系统之间的这个间隙,是 Outbox 要处理的问题。下面先看业务记录与待发消息怎样一起保存,再解释为什么后续发送和消费仍然需要处理重复。
版本与资料依据
last_verified:2026-08-24version_scope:stable distributed data patternssource_type:primary-pattern literature + engineering synthesisstability:stable-concept
问题
数据库提交与 broker publish 是两个系统,普通代码无法让二者天然原子。Transactional outbox 在同一数据库事务写业务记录和 outbox row,独立 relay 发布;consumer 仍可能收到重复消息,因此必须幂等。
BEGIN;
INSERT INTO orders(id, status) VALUES (:id, 'CREATED');
INSERT INTO outbox(event_id, aggregate_id, event_type, payload)
VALUES (:eventId, :id, 'OrderCreated', :json);
COMMIT;
Idempotency key 需要定义 scope、request fingerprint、result retention、并发竞争和过期策略。只在 controller 查一次 key 存在竞态;应使用 unique constraint/transaction 让“首次创建”原子。
Failure simulation
在 commit 后 publish 前 kill 进程、重复 relay、consumer 处理后 ack 前崩溃、乱序和 poison message。验收要求最终状态正确、重复不产生二次副作用、trace 可从业务 ID 关联 event ID。
沿着一次崩溃看 Outbox 的作用
把订单保存和待发事件放进同一个数据库事务后,提交前失败会一起回滚;提交后进程退出,待发事件仍可由后续发布程序找到。这解决的是“业务保存了,却没有留下待通知记录”的间隙。
它没有消灭重复:发布程序可能已经把消息发出去,却没记下发送成功。恢复后再发一次,消费者会收到相同事件 ID。因此去重记录与数据库业务修改应处在同一事务边界;若副作用是外部邮件或支付,还需要对方的幂等契约或状态核对。练习中分别在这两个位置中断,检查数据,不只检查日志。
本节的核验依据
2026-10-25 查阅。下面的链接支持本节的机制说明;练习场景和选型建议是本教程的教学设计。