分布式数据一致性
分布式系统中,数据一致性是架构设计的核心挑战。本文从 CAP/Base 理论出发,系统梳理一致性模型、分布式事务协议(2PC/3PC/TCC/Saga)以及实际工程中的折中方案。
1. CAP 与 BASE 理论
1.1 CAP 定理
在分布式系统中,**一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)**三者不可兼得,最多同时满足两个。
Consistency
/\
/ \
/ \
/------\ CP 系统:ZooKeeper、etcd、Consul、HBase
/ CAP \ AP 系统:Cassandra、DynamoDB、Eureka
/----------\ CA 系统:单机数据库(非分布式)
Partition Tolerance — Availability
| 特性 | 说明 |
|---|---|
| C(一致性) | 所有节点看到的数据一致(等同于所有节点访问的都是最新副本) |
| A(可用性) | 每个请求都能收到非错误响应(不保证数据最新) |
| P(分区容错) | 网络分区时系统仍能继续运行 |
注意:P 在分布式系统中是不可放弃的,所以实际上是在 C 和 A 之间做选择。网络分区是常态(节点故障、网络抖动),因此真正的决策是 CP vs AP。
1.2 BASE 理论
BASE 是 **Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)**的缩写,是对 CAP 中 AP 方案的延伸。
| 概念 | 说明 | 示例 |
|---|---|---|
| 基本可用 | 允许响应时间变长、部分功能降级 | 高峰期排队、返回缓存数据 |
| 软状态 | 允许中间状态,数据不一致时间窗口 | 订单"支付中" → “已支付” |
| 最终一致 | 不保证实时一致,但保证一段时间后一致 | 主从复制延迟、DNS 传播 |
2. 一致性模型
2.1 一致性强度谱系
强一致性 ──────────────────────────────→ 弱一致性
线性一致性 > 顺序一致性 > 因果一致性 > 会话一致性 > 最终一致性 > 弱一致性
线性一致性:所有操作看起来像是按全局时钟顺序执行的(最强)
顺序一致性:所有操作按某种全局顺序执行,但不保证与物理时间一致
因果一致性:有因果关系的操作按顺序执行,无因果关系的可并发
最终一致性:不保证立即一致,但保证在没有新更新的情况下最终一致
2.2 典型系统的一致性选择
| 系统 | 一致性模型 | 原因 |
|---|---|---|
| ZooKeeper | 线性一致性(CP) | 配置管理需要强一致 |
| etcd | 线性一致性(CP) | 服务发现、领导选举 |
| Cassandra | 可调(通常最终一致) | 高写入吞吐 |
| MongoDB | 默认最终一致,可选强一致 | 灵活配置 |
| MySQL 主从 | 最终一致(异步复制) | 性能优先 |
| Redis Cluster | 最终一致 | 异步复制 |
3. 分布式事务
3.1 两阶段提交(2PC)
协调者 参与者(甲) 参与者(乙)
│ │ │
│──── Phase 1: Prepare ─→│ │
│───────────────────────→│──── Prepare ──────────→│
│ │ │
│←──── Yes/No ───────────│←──── Yes/No ───────────│
│ │ │
│ 如果全部 Yes: │ │
│──── Phase 2: Commit ──→│ │
│───────────────────────→│──── Commit ───────────→│
│ │ │
│ 如果有 No: │ │
│──── Phase 2: Abort ───→│ │
│───────────────────────→│──── Abort ────────────→│
2PC 的问题:
| 问题 | 说明 |
|---|---|
| 同步阻塞 | Prepare 阶段参与者锁定资源,等待协调者指令 |
| 单点故障 | 协调者宕机,参与者长期锁定 |
| 数据不一致 | 协调者宕机后恢复,部分参与者已提交,部分未提交 |
// 伪代码示意
interface TwoPhaseCommit {
// Phase 1
boolean prepare(Transaction tx); // 参与者:锁定资源,写 redo/undo log
// Phase 2
void commit(Transaction tx); // 参与者:真正提交,释放锁
void rollback(Transaction tx); // 参与者:回滚,释放锁
}
3.2 三阶段提交(3PC)
引入预提交阶段,降低协调者单点故障的影响。
CanCommit → PreCommit → DoCommit
改进:
- CanCommit:协调者询问参与者是否能执行(不锁定资源)
- PreCommit:参与者预执行,锁定资源
- DoCommit:真正提交
超时处理:
- 参与者在等待 PreCommit 时超时 → 直接中止
- 参与者在等待 DoCommit 时超时 → 认为已提交(单方决策)
仍存在的问题:网络分区时仍可能不一致
3.3 TCC(Try-Confirm-Cancel)
业务层面的分布式事务,将每个操作拆分为三个幂等方法。
public interface PaymentService {
// Try:预留资源(如冻结账户余额)
boolean tryPay(String accountId, BigDecimal amount);
// Confirm:真正执行扣款
boolean confirmPay(String accountId, BigDecimal amount);
// Cancel:释放预留资源(解冻余额)
boolean cancelPay(String accountId, BigDecimal amount);
}
执行流程:
TCC 协调器
├── Try 账号服务(冻结 100 元) ──→ 成功
├── Try 库存服务(预占库存) ──→ 成功
├── Try 优惠券服务(核销优惠券) ──→ 失败
│
└── 任一 Try 失败 → 执行所有 Cancel
├── Cancel 账号服务(解冻)
└── Cancel 库存服务(释放库存)
若全部 Try 成功:
└── 执行所有 Confirm
├── Confirm 账号服务(真正扣款)
├── Confirm 库存服务(真正出库)
└── Confirm 优惠券服务(真正核销)
TCC 优缺点:
- 优点:无全局锁,性能高,业务层实现
- 缺点:业务侵入性高,需写三份逻辑,幂等性要求高
3.4 Saga 模式
长事务拆分成本地事务序列,每个本地事务有对应的补偿操作。
正向流程(事务链):
T1: 创建订单 → T2: 扣减库存 → T3: 扣减余额 → T4: 发送通知
如果 T3 失败:
执行补偿链(倒序):
C3: 恢复余额 ← C2: 恢复库存 ← C1: 取消订单
T1 T2 T3(fail) T4
↓ ↓ ↓
[✓] [✓] [✗]
↑ ↑
C2 ←── C3(补偿)
C1 ←──┘
两种 Saga 实现:
| 类型 | 协调方式 | 代表框架 | 适用场景 |
|---|---|---|---|
| 编排型(Choreography) | 事件驱动,各服务监听事件自行决策 | Axon、Eventuate | 简单流程 |
| 编排型(Orchestration) | 中央协调器统一调度 | Camunda、Netflix Conductor | 复杂流程 |
3.5 Seata 框架(阿里开源)
整合了 AT、TCC、Saga、XA 四种模式的一站式分布式事务解决方案。
Seata 三大角色:
TC(Transaction Coordinator):事务协调器,维护全局事务状态
TM(Transaction Manager):事务管理器,定义全局事务范围
RM(Resource Manager):资源管理器,管理分支事务资源
┌────→ RM(订单服务)
TM(@GlobalTransactional)──→ TC(Seata Server)──┼────→ RM(库存服务)
└────→ RM(账户服务)
AT 模式(自动补偿):
- Seata 代理数据源,自动记录前后镜像(Undo Log)
- 全局提交:异步删除 Undo Log
- 全局回滚:用 Undo Log 生成反向 SQL 回滚
-- 业务 SQL
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- Seata 自动记录的 Undo Log
{
"beforeImage": {"balance": 500},
"afterImage": {"balance": 400},
"sql": "UPDATE account SET balance = balance - 100 WHERE id = 1"
}
-- 回滚时生成的反向 SQL
UPDATE account SET balance = 500 WHERE id = 1;
4. 一致性方案的选择
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 强一致 + 短事务 | 2PC / XA | 金融转账、库存扣减(绝对一致) |
| 最终一致 + 长事务 | Saga + 补偿 | 电商订单全流程 |
| 高并发 + 性能优先 | TCC | 秒杀、抢购 |
| 简单场景 + 框架支持 | Seata AT | 低侵入,自动生成补偿 |
| 跨异构系统 | 消息队列 + 最大努力通知 | 对账补单机制 |
5. 实际工程中的折中
电商下单场景的一致性强弱选择:
┌─────────────┬──────────────┬────────────────┐
│ 操作 │ 一致性要求 │ 实现方案 │
├─────────────┼──────────────┼────────────────┤
│ 库存扣减 │ 强一致 │ 数据库事务 + 乐观锁 │
│ 余额扣减 │ 强一致 │ TCC / 2PC │
│ 订单创建 │ 强一致 │ 本地事务 │
│ 积分增加 │ 最终一致 │ 消息队列异步 │
│ 短信通知 │ 最终一致 │ 消息队列异步 │
│ 搜索索引更新 │ 最终一致 │ Canal + ES │
│ 数据统计 │ 最终一致 │ 定时汇总 │
└─────────────┴──────────────┴────────────────┘
原则:能异步的就异步,能最终一致的不强求强一致
6. 总结
一致性强弱与性能的关系:
强一致(2PC/TCC/XA) ←──── 高延迟、低吞吐 ────→ 最终一致(异步消息)
↑ ↑
数据安全 系统性能
工程中的黄金法则:
1. 核心业务(资金、库存)用强一致
2. 非核心业务用最终一致 + 补偿机制
3. 建立对账和补偿Job,作为最终兜底线
4. 监控一致性延迟指标,设定告警阈值
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。