前言

分布式一致性是企业系统里最容易被问深的一类题目。面试常见问题包括:

  • 分布式锁为什么不是银弹。
  • 本地事务和 MQ 怎么保持一致。
  • 最终一致性怎么理解。
  • TCC、Saga、补偿事务有什么区别。
  • 什么时候可以不用分布式锁。

为什么会有一致性问题

在单体应用里,数据库事务能把很多事情一次性包住。

但到了分布式系统里:

  • 下单服务和库存服务不在一个进程。
  • 支付系统和订单系统不在一个事务里。
  • MQ、Redis、数据库、第三方接口各自独立。

所以必须在“强一致、可用性、性能、复杂度”之间做取舍。

flowchart TD
    A[一个业务动作] --> B[数据库]
    A --> C[Redis]
    A --> D[MQ]
    A --> E[第三方接口]
    B --> F[原子性边界]
    C --> G[缓存一致性]
    D --> H[消息最终一致]
    E --> I[外部系统不确定性]

分布式锁什么时候用

适合:

  • 定时任务抢占。
  • 短临界区互斥。
  • 防重复执行。
  • 分布式资源竞争。

不适合:

  • 复杂长事务。
  • 需要跨多个系统强原子完成的流程。
  • 依赖多个阶段补偿的业务。

方案一:Redis 分布式锁

优点:

  • 快。
  • 简单。

缺点:

  • TTL、续期、主从切换问题。
  • 只能保证互斥,不保证整体业务一致。

方案二:ZooKeeper/etcd 锁

优点:

  • 一致性协调能力更强。

缺点:

  • 成本更高。

方案三:数据库锁或唯一约束

优点:

  • 和业务数据绑定。

缺点:

  • 并发能力弱于缓存类方案。
方案 优点 缺点 场景
Redis 锁 快、轻量 失效和一致性问题 短临界区
ZooKeeper/etcd 强协调 成本高 选主、调度
数据库锁 贴近数据 性能弱 强约束更新

本地事务和消息一致性

这是企业里最经典的一致性题。

场景:

  1. 订单服务写本地数据库。
  2. 同时发一条消息通知库存、营销、积分系统。
  3. 如果消息发成功但数据库回滚,就会脏。
  4. 如果数据库成功但消息没发出去,就会漏。

常见方案:

方案一:事务消息

先写半消息,再执行本地事务,成功后提交。

优点:

  • 较自然地解决本地事务和消息一致性。

缺点:

  • 平台依赖强。

方案二:本地消息表

先写业务数据和本地消息表,再异步投递消息。

优点:

  • 通用。
  • 可补偿。

缺点:

  • 需要后台投递任务。

方案三:Outbox 模式

业务表和事件表放在一起,靠增量同步发布事件。

优点:

  • 工程上稳定。

缺点:

  • 需要额外同步机制。
sequenceDiagram
    participant S as 业务服务
    participant D as 数据库
    participant M as MQ

    S->>D: 写业务记录
    S->>D: 写 outbox 事件
    D-->>M: 同步发布事件
    M->>下游: 消费事件

最终一致性怎么理解

最终一致性不是“永远不一致”,而是:

  • 允许短暂不一致。
  • 通过重试、补偿、对账,最终收敛到一致。

典型场景:

  • 支付成功后订单状态稍后更新。
  • 库存预占后异步确认。
  • 发券失败后补偿。

面试可以这样说:

企业里很多分布式流程做不到强一致,就用最终一致性。核心是有补偿、有对账、有幂等,而不是只靠一次成功。

TCC、Saga、补偿事务

TCC

Try、Confirm、Cancel。

适合:

  • 金融、库存、强控制场景。

缺点:

  • 开发成本高。

Saga

把长事务拆成多个本地事务,每步失败就执行补偿动作。

适合:

  • 长链路业务。

缺点:

  • 补偿逻辑复杂。

补偿事务

以业务补偿为主,不追求强原子。

适合:

  • 订单、物流、售后。

对比:

方案 优点 缺点
TCC 控制强 实现复杂
Saga 适合长链路 补偿难
补偿事务 工程实用 业务语义要设计好

幂等为什么是基础能力

无论是锁、MQ、事务消息还是补偿,最后都离不开幂等。

常见幂等手段:

  • 业务唯一键。
  • 状态机条件更新。
  • 去重表。
  • Redis 去重。
  • 数据库唯一约束。
1
2
3
update pay_order
set status = 'SUCCESS'
where pay_no = ? and status = 'INIT';

对账和补偿

企业系统里真正的兜底通常靠:

  • 定时对账。
  • 差异补偿。
  • 失败重跑。
  • 人工处理。

举例:

  • 支付对账发现成功但订单未支付,补写订单状态。
  • 库存对账发现预占未释放,做释放补偿。
  • MQ 积压时按批次修复。

高频面试题

分布式锁为什么不是银弹?

因为它只能解决互斥,不能天然保证跨系统一致。长链路业务还需要幂等、补偿、对账和最终一致设计。

本地事务和消息怎么保证一致?

常见方案是事务消息、本地消息表或 Outbox 模式。

最终一致性怎么落地?

通过异步消息、重试、补偿、对账和状态机收敛最终状态。

TCC 和 Saga 怎么选?

TCC 控制更强但复杂;Saga 更适合长链路;业务简单时补偿事务往往更实用。

为什么分布式系统一定要做幂等?

因为网络超时、重试、重复消息、重复回调、重复任务都很常见。

总结

分布式一致性回答可以围绕五条主线:

  1. 分布式锁只解决互斥,不解决全部一致性。
  2. 消息一致性靠事务消息、本地消息表、Outbox。
  3. 最终一致性靠重试、补偿、对账。
  4. TCC、Saga、补偿事务各有边界。
  5. 幂等是所有分布式方案的底座。

企业里真正可靠的分布式设计,通常不是“一个大事务”,而是“拆分、异步、补偿、收敛”。