📦 开源仓库:learn_outbox_pattern · 纯 Python 标准库 · 学习演示项目
「本地事务提交了,但发出去的消息没了」——这是事件驱动架构最经典的坑。先提交 DB 再发 MQ,中间崩了事件就丢;先发 MQ 再提交 DB,MQ 发了事务却回滚,下游对着幻影订单瞎忙。「本地事务」和「MQ 发送」在没有分布式事务的前提下没法原子。Outbox 模式绕开它:提交时根本不发 MQ。
核心思路
订单服务(同一个库)
┌─────────────────────────────────────┐
│ orders 表 outbox 表 │
│ (业务数据) (待发事件) │
│ └────┬───────────┬────┘ │
│ 同一个本地事务同时写入两者 │
└─────────────┬───────────┬───────────┘
│ │
中继进程轮询 outbox(PENDING)
│
▼
消费服务(幂等:记录已处理的 event_id)
- 创建订单 = 订单行 + outbox 事件在同一个本地事务里插入
- 独立的**中继(relay)**进程轮询 outbox,把 PENDING 事件投递给消费者后标记 DISPATCHED
- 中继崩了事件安然躺在 outbox 里,重启继续投
- 消费者必须幂等:同一个 event_id 重复投递只 ack,不重复处理
为什么值得学
- 不用 2PC、不用分布式事务
- 本地事务边界小而快
- at-least-once 投递 + 幂等消费者 = 实际恰好一次
- 中继可以随意扩容、监控、重启,事件不丢
演示场景
- 订单创建、中继宕机 → 事件留在 outbox 里 PENDING,不丢
- 中继启动 → 排空 outbox,消费者处理事件
- 中继重复投递 → 消费者按 event_id 去重,不重复处理
- 中继宕机期间下单、恢复后追赶 → 不丢不重
运行
python run_all.py
纯 Python 标准库,零第三方依赖,一键跑完所有演示场景后自动退出。
仓库:github.com/yxpil/learn_outbox_pattern,欢迎 Star 与提 Issue。