事务型 Outbox 如何避免事件丢失?
TOPIC SOURCE
为 Webhook 接收端实现签名校验与防重放:从原始请求体到幂等处理
事务型 Outbox 的核心价值,是消除“写数据库”与“发消息”这两个独立操作之间的不一致。如果业务状态变更已经提交,而消息发送因进程崩溃、网络中断或消息中间件故障失败,事件就会永久丢失;反过来,若先发消息再写库,又可能发出一条并未真正生效的事件。Outbox 把这两步合并成一次本地事务,从根本上回避了跨系统的双写问题。
把事件写入与业务变更放进同一事务
关键做法是:在修改业务数据的同一个数据库事务里,向一张专门的待发送记录表插入事件。两者要么一起提交,要么一起回滚,不存在“业务已改、事件未留”的中间状态。这一步让事件的持久化获得了与业务数据相同的可靠性保证,而不再依赖消息系统在请求当下是否可用。只要事务提交成功,事件就已经被稳定记录,后续即使投递环节暂时失败也能恢复。
由后台任务负责可靠投递
事务提交后,再由独立的后台任务读取待发送记录,将事件推送到外部系统,并记录每条记录的处理状态。这种拆分意味着投递采用至少一次语义:任务失败时可以重试,直到确认成功后才标记完成。需要注意的是,不应在业务事务内同步调用外部服务,否则外部系统的延迟或故障会重新污染本地事务的可靠性。
用幂等键约束重复副作用
至少一次投递必然带来重复,因此接收端必须具备幂等能力。每条事件应携带一个稳定的事件标识,后台任务按该标识执行,外部系统若支持幂等键也应传递同一标识。幂等判断要由数据库唯一约束或等效的原子机制保护,而不能只靠“先查询再写入”的应用层逻辑,否则并发请求可能同时判定记录不存在而重复执行。
Outbox 并非万能,它解决的是本地事务与异步投递之间的一致性,而消费端的去重、状态校验与投递监控仍需配套。只有当原子写入、可恢复的重试与幂等消费共同成立时,事件才能既不丢失,也不会被重复生效。

参与讨论
Outbox 这个方案真是解决了一大痛点