经过几十年的探索,我们终于找到了在全球规模下同时获得强一致性和高可用性的方法。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 的方案:

  1. 每个数据中心部署原子钟和 GPS 接收器
  2. 用它们校准服务器时钟
  3. 时钟误差控制在 几毫秒以内

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:对比

维度SpannerTiDB
时间方案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 系统

全系列回顾

让我们用一张表总结这个系列:

年代方案核心思想适用场景
1970s2PC协调者统一决定小规模、可靠网络
1990sPaxos多数派共识需要强一致、可容忍延迟
2007Dynamo最终一致、AP 优先高可用、可容忍短暂不一致
2010sCRDT数学保证合并协作编辑、离线优先
2012SpannerTrueTime + Paxos全球部署、强一致、有预算
2014Raft简化的 Paxos需要强一致、更易实现
2015+TiDBTSO + Raft强一致、成本敏感

写给开发者的建议

1. 先问业务需求

在选择一致性方案前,先问自己:

  • 不一致会导致金钱损失吗? → 强一致
  • 不一致会导致用户投诉吗? → 看严重程度
  • 短暂不一致可以接受吗? → 最终一致
  • 需要离线支持吗? → CRDT

2. 不要过度设计

规模建议
单机能搞定用 PostgreSQL,别折腾
读多写少主从复制 + 读写分离
写也很多分库分表或 NewSQL
全球部署Spanner 类方案

大多数系统不需要分布式事务。

3. 理解 CAP 的真正含义

CAP 不是让你「三选二」,而是告诉你:

当网络分区发生时,你必须在 C 和 A 之间选择。

  • 网络正常时:你可以同时有 C 和 A
  • 分区发生时:你必须选择等待(C)或继续服务(A)
  • 分区恢复后:你需要处理冲突

4. 监控和报警

无论选择什么方案,都要监控:

指标原因
复制延迟主从不一致的窗口
事务耗时分布式事务的代价
锁等待时间并发冲突程度
分区检测脑裂风险

总结

分布式一致性的演进,本质上是在三个目标之间寻找平衡:

  1. 一致性:所有人看到同样的数据
  2. 可用性:系统始终能响应请求
  3. 性能:响应足够快

没有银弹,只有权衡:

选择得到失去
强一致数据正确性能、可用性
最终一致高可用、低延迟短暂不一致
CRDT自动合并数据类型受限
Spanner全球强一致成本高昂
TiDB强一致 + 开源TSO 网络延迟

最终建议

先理解业务需求,再选择技术方案。

大多数场景,PostgreSQL + Redis 就够了。

当你真正需要分布式一致性时,你会知道的。


理论讲完了,但知道「有哪些方案」和「会用」是两回事。

下一篇,我们进入实战:库存扣减怎么防超卖、分布式锁怎么才安全、跨服务调用怎么保证一致性。


上一篇:CRDT:无需协调的合并魔法

下一篇:实战篇:方案选型与落地

本系列:

  1. 单机到分布式:一致性为何变难
  2. 2PC 与 CAP:理想的破灭
  3. Paxos 与 Raft:让多数人达成共识
  4. 最终一致性:不强求,但终会一致
  5. CRDT:无需协调的合并魔法
  6. 现代方案:从 Spanner 到 TiDB(本篇)
  7. 实战篇:方案选型与落地