Outbox Pattern 工程实践:数据库提交成功但消息发送失败怎么办

Outbox Pattern 用一张本地消息表把业务状态变更和待发送事件放进同一个数据库事务,再异步投递到消息队列。

Outbox Pattern 工程实践:数据库提交成功但消息发送失败怎么办

服务更新数据库后再发送消息,是最容易出问题的地方。

如果数据库提交成功,消息发送失败,下游收不到事件。如果先发消息,再写数据库失败,下游可能看到并不存在的业务事实。

Outbox Pattern 的思路是:业务表和消息表在同一个本地事务里提交。

Rendering diagram...

Outbox 表设计

CREATE TABLE outbox_events (
  id BIGINT PRIMARY KEY,
  event_id VARCHAR(128) NOT NULL UNIQUE,
  event_type VARCHAR(128) NOT NULL,
  aggregate_id VARCHAR(128) NOT NULL,
  payload JSON NOT NULL,
  status VARCHAR(32) NOT NULL,
  created_at TIMESTAMP NOT NULL,
  sent_at TIMESTAMP NULL
);

业务操作时写入 outbox。

BEGIN;

UPDATE orders
SET status = 'PAID'
WHERE id = 10001;

INSERT INTO outbox_events(event_id, event_type, aggregate_id, payload, status, created_at)
VALUES ('evt_10001_paid', 'OrderPaid', '10001', '{"order_id":10001}', 'NEW', now());

COMMIT;

投递方式

第一种是定时任务扫描 NEW 状态事件,发送到 MQ,成功后标记 SENT

第二种是 CDC 监听 outbox 表变更,把新增事件投递到 Kafka。Debezium Outbox Event Router 就是这种思路。

重试和幂等

投递任务可能发送成功但更新 SENT 失败,于是下次会重复发送。所以消息必须带 event_id,消费者必须幂等。

Outbox 解决的是发送可靠性,不解决消费端业务幂等。

清理策略

Outbox 表会持续增长,需要定期归档或删除已发送事件。

可以保留最近 7 到 30 天,用于排查和重放。更久的事件进入归档表或对象存储。

小结

Outbox Pattern 是业务系统实现最终一致性的朴素但有效方案。它把“业务变化”和“待发送事件”放进一个本地事务,再通过异步投递完成事件传播。

参考链接