外观主题
阅读语言

Outbox 模式 —— 业务与事件同事务落库,中继保证投递

软件 本站软件 1小时前
https://yxp.hk/p/0Wx5SA

📦 开源仓库: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 投递 + 幂等消费者 = 实际恰好一次
  • 中继可以随意扩容、监控、重启,事件不丢

演示场景

  1. 订单创建、中继宕机 → 事件留在 outbox 里 PENDING,不丢
  2. 中继启动 → 排空 outbox,消费者处理事件
  3. 中继重复投递 → 消费者按 event_id 去重,不重复处理
  4. 中继宕机期间下单、恢复后追赶 → 不丢不重

运行

python run_all.py

纯 Python 标准库,零第三方依赖,一键跑完所有演示场景后自动退出。
仓库:github.com/yxpil/learn_outbox_pattern,欢迎 Star 与提 Issue。