经过几十年的探索,我们终于找到了在全球规模下同时获得强一致性和高可用性的方法。Google Spanner 用原子钟改写了规则,TiDB 用软件方案让它平民化。
前情回顾
在这个系列中,我们经历了分布式一致性的演进:
| 篇章 | 主题 | 核心思想 |
|---|---|---|
| 第一篇 | 为什么难 | 网络不可靠、时钟不一致、节点会挂 |
| 第二篇 | 2PC 与 CAP | 单点问题、不可能三角 |
| 第三篇 | Paxos/Raft | 多数派共识、自动选主 |
| 第四篇 | 最终一致性 | 用一致性换可用性 |
| 第五篇 | CRDT | 数学保证自动合并 |
现在的问题:能不能既要强一致性,又要高可用性,还要全球部署?
传统答案是:不能,CAP 定理说了。
但 Google 说:可以。
2012 年:Spanner 论文
Google 在 2012 年发表了 Spanner 论文,声称实现了「外部一致性」——比强一致性更强的保证。
外部一致性 vs 线性一致性:
| 一致性级别 | 含义 | 关键区别 |
|---|---|---|
| 线性一致性 | 操作看起来在调用和返回之间的某个点原子执行 | 只保证逻辑顺序,不关心真实时间 |
| 外部一致性 | 如果事务 T1 在事务 T2 开始前提交,那 T1 的时间戳一定 < T2 | 按真实时间排序,更强 |
简单说:线性一致性是「看起来像单机」,外部一致性是「真的按时间顺序」。
怎么做到的? 用硬件解决软件问题:原子钟 + GPS。
回顾:为什么时钟是问题
第一篇我们说过,分布式系统不能用物理时钟判断顺序,因为时钟会漂移。
机器 A (时钟快) 机器 B (时钟慢)
│ │
10:00:05 写入 x=1 │
│ │
│ 10:00:00 写入 x=2
│ │
时间戳说 A 更晚
实际上 B 更晚
结论:时间戳不可信。
Spanner 的思路:让时间可信
如果时钟足够精确呢?
Google 的方案:
- 每个数据中心部署原子钟和 GPS 接收器
- 用它们校准服务器时钟
- 时钟误差控制在 几毫秒以内
TrueTime API
Spanner 不返回「当前时间」,而是返回一个时间区间:
TrueTime.now() 返回:
[earliest, latest]
含义:
真实时间一定在这个区间内
例如:
[10:00:00.001, 10:00:00.007]
真实时间在 1ms 到 7ms 之间
误差范围:6ms
关键洞察:我不知道精确时间,但我知道误差范围。
如何用 TrueTime 保证顺序
规则:提交事务时,等待误差时间过去。
事务提交流程:
│
│ 1. 获取 TrueTime: [10:00:00.001, 10:00:00.007]
│
│ 2. 选择提交时间戳:latest = 10:00:00.007
│
│ 3. 等待直到 TrueTime.now().earliest > 10:00:00.007
│ (等待约 6-7ms)
│
│ 4. 提交事务
│
▼
为什么要等?
假设不等,立刻提交:
事务 A 在机器 1:时间戳 10:00:00.007
事务 B 在机器 2:时间戳 10:00:00.005
如果 B 实际发生在 A 之后,但时间戳更小
读取时会认为 A 比 B 新,顺序错了
等待后:
A 提交时,全球所有机器的时钟都已经过了 10:00:00.007
之后的事务时间戳一定 > 10:00:00.007
顺序得到保证
代价是什么
| 优点 | 代价 |
|---|---|
| 全球强一致 | 每次提交等待 ~7ms |
| 事务顺序保证 | 需要原子钟硬件 |
| 可以用时间戳排序 | 成本极高 |
这个延迟值不值?
对于 Google 的规模:值。跨洲际网络延迟本来就是 100ms+,多等 7ms 不算什么。
但普通公司买不起原子钟。
TiDB:软件方案
TiDB 是 PingCAP 开发的开源分布式数据库,目标是让普通公司也能用上 Spanner 级别的能力。
问题:没有原子钟,怎么保证时间顺序?
答案:用一个中心化的时间戳服务——TSO(Timestamp Oracle)。
TSO 的工作原理
┌─────────┐
│ TSO │
│ Server │
└────┬────┘
│
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
TiDB 1 TiDB 2 TiDB 3
每个事务开始时:
向 TSO 请求一个时间戳
TSO 保证:
返回的时间戳严格递增
后请求的一定比先请求的大
简单粗暴但有效:不用物理时间,用逻辑时间。
TSO 的实现细节
TSO 时间戳格式(64位):
┌────────────────┬──────────────┐
│ 物理时间 (ms) │ 逻辑计数器 │
│ 46 bits │ 18 bits │
└────────────────┴──────────────┘
物理部分:当前毫秒时间
逻辑部分:同一毫秒内的序号
例如:
第一个请求:1702000000000 << 18 | 1
第二个请求:1702000000000 << 18 | 2
...
下一毫秒: 1702000000001 << 18 | 1
优点:
- 严格递增,顺序明确
- 包含物理时间,可读性好
- 实现简单
TSO 的问题:单点
问题:TSO Server 是单点,挂了怎么办?
解决方案:TSO 集群 + Raft
┌─────────────────────────┐
│ TSO 集群 │
│ ┌───┐ ┌───┐ ┌───┐ │
│ │ L │ │ F │ │ F │ │
│ └───┘ └───┘ └───┘ │
│ Leader Follower │
└─────────────────────────┘
- Leader 负责分配时间戳
- Follower 做热备
- Leader 挂了,用 Raft 选新 Leader
- 新 Leader 从上次的最大值继续分配
另一个问题:延迟
问题:每个事务都要请求 TSO,网络延迟成为瓶颈。
优化方案:批量获取
优化前:
事务 1 → TSO → 获取 ts=100
事务 2 → TSO → 获取 ts=101
事务 3 → TSO → 获取 ts=102
每个事务一次网络往返
优化后:
TiDB → TSO → 批量获取 ts=100~199
事务 1 → 本地分配 ts=100
事务 2 → 本地分配 ts=101
...
事务 100 → 本地分配 ts=199
100 个事务只需要一次网络往返
Spanner vs TiDB:对比
| 维度 | Spanner | TiDB |
|---|---|---|
| 时间方案 | TrueTime(原子钟) | TSO(逻辑时钟) |
| 部署成本 | 极高 | 普通 |
| 跨地域延迟 | ~7ms 等待 | TSO 网络延迟 |
| 一致性 | 外部一致性 | 线性一致性 |
| 开源 | 否(Cloud Spanner 服务) | 是 |
选择建议:
- 有钱 + 全球部署 + 极致一致性:Cloud Spanner
- 普通公司 + 区域部署 + 成本敏感:TiDB / CockroachDB
混合方案:实际生产中的选择
现代系统往往不是纯 CP 或纯 AP,而是混合使用。
典型架构
电商系统架构:
│
├── 订单服务 ──────► TiDB (强一致)
│ └── 库存扣减、支付
│
├── 商品服务 ──────► MySQL + Redis (读写分离)
│ └── 商品信息、价格
│
├── 评论服务 ──────► MongoDB (最终一致)
│ └── 用户评论、评分
│
└── 推荐服务 ──────► Redis Cluster (AP)
└── 个性化推荐、浏览历史
选型原则
| 数据类型 | 一致性要求 | 推荐方案 |
|---|---|---|
| 交易数据 | 强一致 | TiDB, CockroachDB, Spanner |
| 配置数据 | 强一致 | etcd, ZooKeeper |
| 社交数据 | 最终一致 | Cassandra, MongoDB |
| 缓存数据 | 最终一致 | Redis Cluster |
| 协作数据 | 无冲突 | CRDT-based 系统 |
全系列回顾
让我们用一张表总结这个系列:
| 年代 | 方案 | 核心思想 | 适用场景 |
|---|---|---|---|
| 1970s | 2PC | 协调者统一决定 | 小规模、可靠网络 |
| 1990s | Paxos | 多数派共识 | 需要强一致、可容忍延迟 |
| 2007 | Dynamo | 最终一致、AP 优先 | 高可用、可容忍短暂不一致 |
| 2010s | CRDT | 数学保证合并 | 协作编辑、离线优先 |
| 2012 | Spanner | TrueTime + Paxos | 全球部署、强一致、有预算 |
| 2014 | Raft | 简化的 Paxos | 需要强一致、更易实现 |
| 2015+ | TiDB | TSO + Raft | 强一致、成本敏感 |
写给开发者的建议
1. 先问业务需求
在选择一致性方案前,先问自己:
- 不一致会导致金钱损失吗? → 强一致
- 不一致会导致用户投诉吗? → 看严重程度
- 短暂不一致可以接受吗? → 最终一致
- 需要离线支持吗? → CRDT
2. 不要过度设计
| 规模 | 建议 |
|---|---|
| 单机能搞定 | 用 PostgreSQL,别折腾 |
| 读多写少 | 主从复制 + 读写分离 |
| 写也很多 | 分库分表或 NewSQL |
| 全球部署 | Spanner 类方案 |
大多数系统不需要分布式事务。
3. 理解 CAP 的真正含义
CAP 不是让你「三选二」,而是告诉你:
当网络分区发生时,你必须在 C 和 A 之间选择。
- 网络正常时:你可以同时有 C 和 A
- 分区发生时:你必须选择等待(C)或继续服务(A)
- 分区恢复后:你需要处理冲突
4. 监控和报警
无论选择什么方案,都要监控:
| 指标 | 原因 |
|---|---|
| 复制延迟 | 主从不一致的窗口 |
| 事务耗时 | 分布式事务的代价 |
| 锁等待时间 | 并发冲突程度 |
| 分区检测 | 脑裂风险 |
总结
分布式一致性的演进,本质上是在三个目标之间寻找平衡:
- 一致性:所有人看到同样的数据
- 可用性:系统始终能响应请求
- 性能:响应足够快
没有银弹,只有权衡:
| 选择 | 得到 | 失去 |
|---|---|---|
| 强一致 | 数据正确 | 性能、可用性 |
| 最终一致 | 高可用、低延迟 | 短暂不一致 |
| CRDT | 自动合并 | 数据类型受限 |
| Spanner | 全球强一致 | 成本高昂 |
| TiDB | 强一致 + 开源 | TSO 网络延迟 |
最终建议:
先理解业务需求,再选择技术方案。
大多数场景,PostgreSQL + Redis 就够了。
当你真正需要分布式一致性时,你会知道的。
理论讲完了,但知道「有哪些方案」和「会用」是两回事。
下一篇,我们进入实战:库存扣减怎么防超卖、分布式锁怎么才安全、跨服务调用怎么保证一致性。
上一篇:CRDT:无需协调的合并魔法
下一篇:实战篇:方案选型与落地
本系列:
- 单机到分布式:一致性为何变难
- 2PC 与 CAP:理想的破灭
- Paxos 与 Raft:让多数人达成共识
- 最终一致性:不强求,但终会一致
- CRDT:无需协调的合并魔法
- 现代方案:从 Spanner 到 TiDB(本篇)
- 实战篇:方案选型与落地