Damnatiox
DOCUMENT / published

分布式 ID、分布式事务、Outbox、Saga 与可靠事件

分布式 ID、分布式事务、Outbox、Saga 与可靠事件 跨服务一致性先定义业务不变量与失败补偿,再在本地事务、幂等和事件之间选择最小机制。 1. 本文覆盖范围 ID 生成 两阶段提交边界 Transactional Outbox Saga、幂等和对账 2. 核心知识详解 1. 分布式 ID 数据库序列/自增、号段、UUID/ULID 和 Snowflake 类算法在排序、索引局部性、中心依赖和时钟上权衡。 外部公开 ID 避免暴露

分布式与微服务 2026/8/244 分钟阅读
# Java# Java 后端# 分布式与微服务

分布式 ID、分布式事务、Outbox、Saga 与可靠事件

跨服务一致性先定义业务不变量与失败补偿,再在本地事务、幂等和事件之间选择最小机制。

1. 本文覆盖范围

  • ID 生成
  • 两阶段提交边界
  • Transactional Outbox
  • Saga、幂等和对账

2. 核心知识详解

1. 分布式 ID

数据库序列/自增、号段、UUID/ULID 和 Snowflake 类算法在排序、索引局部性、中心依赖和时钟上权衡。

  • 外部公开 ID 避免暴露业务量,可与内部主键分离。
  • Snowflake 类方案必须治理 worker id、时钟回拨和位宽寿命。
  • ID 唯一不等于请求幂等;重试仍需业务幂等键。

正确性边界: 按时间大致有序的 ID 不是严格业务顺序,也不应作为全局时钟。

2. 原子提交协议

XA/2PC 通过协调器和参与者 prepare/commit 获得原子性,但有锁持有、协调和可用性代价;适合边界明确且基础设施支持的场景。

  • 先评估能否重新划分聚合,在单库事务中维护核心不变量。
  • 超时后查询事务状态,参与者恢复流程可演练。
  • 不要用分布式锁代替事务原子性。

正确性边界: 2PC 保证的范围取决于参与资源和实现,并不包含未参与的外部 HTTP 副作用。

3. Transactional Outbox

业务数据和 outbox 事件在同一本地事务写入,独立发布器把事件发送到 broker;消费者按 event id 幂等处理。

  • 事件包含 schema version、aggregate id、sequence 和 trace context。
  • 发布至少一次,因此消费者去重与可重入是必要设计。
  • 监控未发布积压、失败次数和端到端事件延迟。

正确性边界: Outbox 解决数据库与事件记录的原子性,不保证 broker 和所有消费者恰好一次。

4. Saga 与对账

Saga 把长事务拆为本地事务序列,通过编排器或事件协同推进,失败时执行语义补偿。补偿不是数据库回滚,而是新的业务操作。

  • 状态机记录每步、重试、补偿和人工介入。
  • 预留资源设置过期时间,补偿可重复执行。
  • 定期对账发现未达最终状态和跨系统差异。

正确性边界: 某些副作用不可逆;设计时需设置确认点、人工流程或风险准备金。

3. 工程链路

flowchart LR A["本地事务"] --> B["业务表"] A --> C["Outbox"] C --> D["发布器至少一次发送"] D --> E["Broker"] E --> F["幂等消费者"] F --> G["本地事务/状态机"] G --> H["对账与补偿"]

4. 最小可运行示例

下面的示例只保留关键路径。把它放入对应版本的最小工程,先运行测试或命令确认行为,再逐步加入重试、超时、监控和异常分支。

java
@Service class TransferService { @Transactional public void transfer(long from, long to, BigDecimal amount) { accounts.debit(from, amount); accounts.credit(to, amount); } } // 事务方法应从代理外部调用;同类自调用不会经过代理拦截。

5. 实践与验证

  1. 实现订单表与 outbox 同事务写入并模拟发布器崩溃重启。
  2. 为消费者设计去重表和可重入更新。
  3. 画出支付失败、库存释放和补偿失败的 Saga 状态机。

6. 掌握检查

  • 能区分唯一 ID 与幂等键。
  • 能说明 2PC 适用边界。
  • 能实现 Outbox。
  • 能为不可逆步骤设计对账。

参考资料