本页目录
存储系统实例
Dynamo(leaderless + gossip + quorum)和 Spanner(leader-based + TrueTime + Paxos + 2PC)代表分布式存储的两极。把前五章讲过的共识、复制、分区、成员发现拼成两个完整的系统架构。
概述
前面五章分别讲了共识、复制、分区、成员发现、读写路径与修复——这些不是孤立的概念,在真实的分布式存储系统里它们同时运转、互相咬合。这篇选两个经典系统,把前面的零件拼成整车:Dynamo(leaderless + gossip + quorum 路线)和 Spanner(leader-based + TrueTime + 外部一致性 路线)。两个系统代表分布式存储的两极,看懂它们就懂了前面每一章为什么要那么讲。
Dynamo:可用性优先的拼图
Amazon 2007 年的 Dynamo 论文奠定了 leaderless + eventual consistency 这一整条路线(Riak、Cassandra、DynamoDB 都承自它)。它的架构是几个独立机制的组合,每个机制前面都已单独讲过——把这几个拼起来,就是 Dynamo:
| 层 | Dynamo 的做法 | 对应前文章节 |
|---|---|---|
| 分区 | 一致性哈希,每节点负责一段 token range;引入虚拟节点(每物理节点 ~100 个 vnode)让负载更均匀 | 一致性哈希 |
| 复制 | 每条数据写到 coordinator 后,按顺时针方向写 N 个后继(vnode 所在物理节点),保证副本分散在不同物理节点 | 复制策略 leaderless |
| 写 | coordinator 并发写 N 个副本,等 W 个 ACK 即返回;W < N 时部分副本暂时不一致 | 分布式读写路径 |
| 读 | coordinator 并发读 N 个副本,等 R 个响应,取版本最新的(比较向量时钟,不是时间戳);触发 read repair 异步推最新版本给落后副本 | 同上 |
| 成员发现 | gossip 协议传播节点加入/离开/心跳信息,最终每个节点都有完整的路由表 | Gossip 协议 |
| 反熵 | 后台 Merkle tree 比对, gossip 驱动修复 | 反熵与数据修复 |
| 冲突解决 | 向量时钟(vector clock)追踪因果;应用端在读取时合并冲突(购物车:合并不同副本的条目,别丢) | 冲突解决 |
| hinted handoff | 目标节点不可达时,coordinator 把它的那份暂存在自己的 hints 区,等目标恢复后回放 | 分布式读写路径 hint 段 |
这几层咬合时有两个全局约束贯穿全栈:
- 始终可写(always writeable)——即使部分节点不可达,coordinator 也接受写(hinted handoff + sloppy quorum 保证 W 凑够),代价是冲突可能更频繁。
- 最终一致性——数据最终收敛,但收敛前的窗口内,不同读可能看到不同版本。应用层要处理这种事(Dynamo 把冲突推给应用端解决,这正是它设计哲学的核心:存储层不替应用做冲突决策)。
一个 Dynamo 写的完整旅程(把所有层串起来)
Spanner:外部一致性的拼图
Google Spanner(2012)选了另一条路:强 leader + 外部一致性,支撑 Google 的广告和 F1 数据库。它同样是一堆机制的组合,但追求的目标完全相反——宁可牺牲可用性,也要保证"像单机一样"的事务语义。
| 层 | Spanner 的做法 | 对应前文章节 |
|---|---|---|
| 分区 | 范围分片(range sharding),key 按字典序切成 tablets,每个 tablet 是一个 Paxos group | 分片策略 |
| 共识 | 每个 tablet 内部是一个 Paxos group(多副本,Paxos 选主并复制日志) | Paxos |
| 复制 | Paxos log 复制到 group 内 majority;所有写必须经 leader;读可以从 follower(但需要读事务时间戳) | 复制策略 single-leader |
| 事务 | 跨 tablet 写走 2PC(两阶段提交),协调者是其中一个 Paxos leader;提交时间戳由 TrueTime 分配 | 分布式事务 |
| 时间 | TrueTime:每个数据中心有 GPS + 原子钟对时,保证全局时钟偏移 ≤ ε(~7ms)。给每个事务分配一个"晚于所有已提交事务"的时间戳,实现外部一致性 | 时间与时钟 |
Spanner 的核心创新是 TrueTime + 2PC + Paxos 的组合,实现 外部一致性(external consistency)——事务按提交时间戳全局有序,任何读都能看到所有早于它的写。这和 Dynamo 的"最终收敛、读可能看到旧数据"完全相反。
一个 Spanner 跨表事务(把所有层串起来)
BEGIN
UPDATE users SET balance = balance - 100 WHERE id = 42 -- 在 tablet A (Paxos group G1)
UPDATE orders SET status = 'paid' WHERE id = 789 -- 在 tablet B (Paxos group G2)
COMMIT
流程:
1. Client → G1 的 Paxos leader (变成 2PC Coordinator)
2. Coordinator: 发 prepare 到 G1 和 G2 的 leader
3. 每个 group 的 Paxos leader: 把 prepare 记录写入 Paxos log → 复制到 majority
4. 所有 participant ACK → Coordinator 选 commit timestamp:
- ts = max(各 participant 本地时间) + 保底 > TrueTime.now() + ε
- 这保证 ts 晚于任何已提交事务的时间戳 (外部一致性)
5. Coordinator: 把 commit record + ts 写入自己的 Paxos log → 复制 → committed
6. 通知各 participant 提交 (Paxos log entry with ts)
注意:第 4 步 TrueTime 的 now() + ε 保证——因为 ε 是时钟偏差上界,所有数据中心"此刻"的真实时间不可能超过 now() + ε,所以选 ts > now() + ε 保证 ts 在将来,任何读在 ts 后都能看到这个事务。这就是 Spanner 能对 SQL 提供"像单机一样"的序列化隔离——因为时间戳是真实的、物理约束的全局序。
两极对比
| Dynamo | Spanner | |
|---|---|---|
| 一致性 | 最终一致性 | 外部一致性(强) |
| 写可用性 | 始终(leaderless,任何节点可接受) | 需要 leader(leader 挂了等选举) |
| 冲突解决 | 应用端 (向量时钟) | 存储层 (时间戳全局序) |
| 事务 | 不支持跨 key 事务 | 完整 ACID (2PC + TrueTime) |
| 延迟 | 低(无多轮协调) | 高(Paxos + 2PC + commit wait) |
| 时钟依赖 | 宽松(只用于版本比较,不要求同步) | 严格(TrueTime 是正确性的前提) |
| 运维复杂度 | 低(无 leader,节点随便挂) | 高(GPS/原子钟对时,leader 选举) |
这两个系统的选择就是分布式存储系统的根本权衡——任何时候在做一个存储选型,本质上是在这张表的两列之间找位置。
参考
- Dynamo 论文: "Dynamo: Amazon's Highly Available Key-value Store"(DeCandia 2007)——所有 leaderless 系统的源头
- Spanner 论文: "Spanner: Google's Globally-Distributed Database"(Corbett 2012)——TrueTime + 外部一致性
Keywords: Dynamo, Spanner, leaderless, leader-based, consistent hashing, virtual nodes, gossip, quorum, Merkle tree, vector clock, eventual consistency, TrueTime, external consistency, Paxos, 2PC, GPS + atomic clock, commit wait, ε, always writeable, timestamp ordering