Skip to content

元数据卡

维度
难度(黑魔法)
前置理解 CAP 定理(第15章)、B+树与 LSM-Tree(第4-5章)
关键词数据分片、一致性哈希、Raft、CockroachDB、Spanner、TrueTime、NewSQL
代码语言伪代码 / SQL / 配置

你的进度

单座数据堡垒装不下所有数据了。老陈决定在七座城市各建一个分仓。但问题来了:各分仓之间的数据怎么分?怎么保证龙鳞分仓的宝物登记和星辉分仓的库存数据是一致的?网络断了怎么办?这就是分布式数据库要解决的问题:跨城市的数据分片、复制、一致性保证。

你的任务

这一章带你走过分布式数据库的最大挑战与设计:

  1. 为什么要用分布式数据库——单机到底卡在哪
  2. 数据分片——怎么把数据分布到多台机器又不失衡
  3. 复制与共识——主从复制和 Raft/Paxos 的区别与代价
  4. CockroachDB 如何做地理分布 SQL
  5. Google Spanner 的 TrueTime 和 Paxos
  6. NewSQL 在做什么

读完这章,你会理解:世界上最好的工程师怎么让分布式数据库看起来像单机数据库一样好用。


破局 · 溯源

1. 为什么需要分布式数据库?

单机数据库能处理的数据量级是什么?

大概的数字是:单间库房——MySQL 或 PostgreSQL——大概能扛几 TB 到十几 TB,配合合理的索引和查询优化,可以稳定运行。超过这个范围后,你开始撞墙:

存储墙。 一张宝物登记表 30TB,索引再翻一倍。一间库房的磁盘装不下。你可以买更大的磁盘——但磁盘扩容有上限。你可以上 RAID 阵列——但文件系统也有上限。

性能墙。 单机数据库的查询吞吐受限于 CPU、内存带宽、磁盘 IOPS。你可以把 CPU 从 8 核升级到 128 核,但提升不是线性的——NUMA 拓扑、内存带宽瓶颈、锁竞争,都会限制你。

地理墙。 你的冒险者分布在全球。总库在龙鳞城——星辉城的守卫查一件宝物,网络一来一回 100ms+。你在全球部署多个数据库,但怎么让它们保持数据一致?

高可用墙。 单间库房宕机=整座堡垒停摆。你可以做主从复制,但主节点挂了手工切从——那 5 分钟可能正好赶上地下溶洞涨水,守卫查不了库、领不了装备。

分布式数据库要解决所有这些墙——单库 → 多库 → 全球分布式

2. 数据分片(Sharding)

把一个数据库的"一张表"拆到多台机器上,根本问题是:怎么决定一条数据去哪台机器?

2.1 范围分片(Range Sharding)

按数据值的范围划分。

Shard 0: adventurer_id 1-10000
Shard 1: adventurer_id 10001-20000
Shard 2: adventurer_id 20001-30000
...

优点:范围查询走顺序扫描高效。"查 adventurer_id 5000 到 12000 的数据"——只需要访问 Shard 0 和 Shard 1。

缺点:数据倾斜(热点)。如果新冒险者涌入分配的都是大 id,Shard N 扛了所有压力,其他 Shard 空转。

2.2 哈希分片(Hash Sharding)

对 key 做哈希,按哈希值分配。

python
shard_id = hash(adventurer_id) % NUM_SHARDS

优点:数据均匀分布的概率高。

缺点:范围查询必须扫描所有 Shard。"查 adventurer 0-10000"——shard_id 不是连续的,必须查所有分片再合并结果。

最致命的:加/减机器时,NUM_SHARDS 变了,几乎所有数据都得重新分配。 这就是著名的"Rehashing 问题"。

2.3 一致性哈希(Consistent Hashing)

一致性哈希是对哈希分片的改进——加/减机器时只需要迁移一小部分数据。

它是怎么做到的?

传统取模:hash(key) % 4
加一台变成 % 5 → 几乎所有 key 的映射都变了

一致性哈希:hash(key) 映射到一个环上(0 ~ 2^32-1)
机器也映射到同一个环上
数据 belongs to 环上顺时针遇到的第一个机器
加一台机器 → 只影响这台机器相邻环段的数据
 
 M0 ← 机器 0 在环上的位置
 
 
 M3M1
 
 
 M2

当加入 M3 时,它接管了 M2 和 M0 之间环段的一部分数据。只有这个环段的数据受影响——其他数据不动。

所以加一台机器,不需要全量再平衡。 这就是一致性哈希对分布式存储的贡献。

虚拟节点(Virtual Nodes):实际部署时,每台物理机器在环上占据多个虚拟位置。这样即使机器数量少,数据分布也更均匀。

实际使用:Cassandra、DynamoDB、CockroachDB、Redis Cluster 都用一致性哈希或其变体。

3. 复制与共识

分片解决了"数据放哪",复制解决了"数据挂了怎么办"。

3.1 主从复制(Leader-Follower Replication)

写入 → Leader → 同步到 Follower(s)
读取 → 可以从 Follower 读(读写分离)

最常用的复制策略之一。MySQL、PostgreSQL 都用它。

同步复制:Leader 等待所有 Follower 确认写入 → 才返回成功。强一致,但延迟高。

异步复制:Leader 写入后立刻返回,Follower 慢慢追。低延迟,但 Leader 挂了没同步的数据就丢了。

半同步复制:Leader 等待至少一个 Follower 确认 → 返回。折中方案。

主从的问题:Leader 挂了要选一个新的 Leader。谁来选?怎么选?这就是共识算法要解决的事。

3.2 Raft 共识算法

Raft 是目前最流行的共识算法(Paxos 的理论更早更复杂,Raft 的设计目标是"可理解")。

Raft 的三个角色

 
 Leader ←→ Follower ←→ Follower 
 (一个) 
 

 所有写操作必须经过 Leader

Leader 选举

1. Follower 等待随机时间(150-300ms)
2. 如果没有听到 Leader 的心跳,变成 Candidate
3. Candidate 拉票(RequestVote RPC)
4. 大多数节点(Quorum)投票给这个 Candidate → 它成为新 Leader

日志复制

1. Client 发写请求给 Leader
2. Leader 追加日志条目到它的本地日志
3. Leader 广播 AppendEntries RPC 给所有 Follower
4. Follower 确认写入日志
5. 大多数确认后 → Leader commit 这条日志
6. Leader 通知 Follower commit

为什么是大多数(Majority/Quorum)?

大多数意味着 N/2 + 1。5 个节点的集群需要至少 3 个节点同意。这保证了任意两个大多数都会交叠——不可能选出两个 Leader,也不可能认为两个不同的日志条目都提交了。

Multi-Paxos vs Raft 的简化视角

Paxos: 每个提案位经历一轮"Prepare → Promise → Accept → Accepted"
 复杂度在理解而非实现

Raft: 连续选举 Leader,Leader 全权负责日志复制
 简化了Paxos的"多轮 Paxos"为"Leader 任期内的单轮 Paxos"

实际工程中:etcd(CoreOS 的分布式 KV)就是用 Raft 做共识。Kubernetes 的所有状态数据存在 etcd 里——etcd 挂了 = Kubernetes 集群挂了。Raft 的 Quorum 机制保证了 etcd 的高可用。

4. CockroachDB:全球分布式 SQL

CockroachDB 的命名灵感来自蟑螂(Cockroach)——怎么踩都踩不死。

CockroachDB 的根本理念:一个 SQL 数据库,跑在全球多个机房,看起来像一个数据库实例。

架构层次


 SQL Layer (SQL → KV) 
 解析 SQL → 优化 → 生成 KV 操作计划 

 Transaction Layer 
 并行 MVCC + 时钟同步 + 事务协调 

 Distribution Layer 
 一致性哈希分片(Ranges) + Raft 复制 

 Storage Layer (LSM-Tree) 
 RocksDB/Pebble:持久化存储

关键设计

Ranges(数据分片):CockroachDB 把数据按主键范围切分成连续的 Ranges(默认 256MB 一个 Range)。每个 Range 是 Raft 里一个独立的共识组。Range 太大就自动分裂,Range 太小就合并。

Geo-Distribution:你决定数据放在哪些区域、每个区域放多少副本、读请求是优先本地还是全局一致。

在法术敕令(SQL)里像这样指定:

sql
ALTER TABLE treasures CONFIGURE ZONE USING
 num_replicas = 3
 constraints = '{+region=us-east: 1 +region=eu-west: 1 +region=ap-southeast: 1}';

这代表 treasures 表在龙鳞城、星辉城、月影城各有一个副本——任何区域的守卫都能低延迟访问本地副本。

事务:CockroachDB 支持 Serializable Snapshot Isolation(SSI)——这是最强的事务隔离级别之一。实现依赖混合逻辑时钟(HLC Hybrid Logical Clock)——在每个节点维护自己的物理时钟 + 逻辑计数器,避免了 Google Spanner 对原子钟(TrueTime)的依赖。

CockroachDB 和 Spanner 的对比

特性CockroachDBSpanner
时钟同步HLC(软件级,无需硬件)TrueTime(GPS + 原子钟)
共识算法RaftPaxos
数据模型SQL + JSONSQL + 层级表
复制单元Range (256MB)Split (分片)
外部一致性无(HLC 做不到)有(TrueTime 保证)
部署自托管 / Cloud只限 Google Cloud

5. Google Spanner:全世界最大的分布式数据库

Spanner 是 Google 的内部分布式数据库——支撑了 AdWords、Google Play、Gmail、YouTube 等产品。2012 年的 Spanner 论文(Spanner: Google's Globally-Distributed Database)是分布式数据库领域的里程碑。

Spanner 的突破:外部一致性(External Consistency)

大多数分布式数据库能做到"最终一致性"或"读已提交"。Spanner 做到了外部一致性——等价于单机数据库的可串行化,而且在全球范围内。

怎么做到的?TrueTime

TrueTime:GPS + 原子钟 ≠ 同步

TrueTime 不是"全球统一的时钟"。它做不到这一点(相对论效应都不允许)。

TrueTime 的做法是:每台 Spanner 服务器有 GPS 和原子钟各一组。实时监控时钟的偏差。每台机器都告诉你:"当前真实时间在 [earliest latest] 之间"

Timestamp 提交时:TT.now().latest
读取时:TT.now().earliest 确保所有早于这个时间的事务都提交了

Spanner 用 TrueTime 给事务打时间戳,保证这些时间戳在全局是唯一的、自增的、和物理时间一致的。然后 Paxos 负责让所有副本对这个时间戳达成一致。

Spanner 的存储结构

 F1 Query Layer
 (Google 用 F1 做 Schema 查询)
 
 
 Spanner 
 SQL 层 
 
 
 
 
 Paxos Group Paxos Group Paxos Group
 (Split 0) (Split 1) (Split 2)
 
 Colossus FS Colossus FS Colossus FS
 (GFS v2) (GFS v2) (GFS v2)

Spanner 不是开源的,但它的设计深刻影响了 CockroachDB、YugabyteDB 等开源项目。

6. NewSQL 在做什么

NewSQL 不是一个具体的产品,而是一个运动。它的目标:把 NoSQL 的水平可扩展性 + RDBMS 的 SQL 和 ACID 结合起来。

NewSQL 的共同特征

  1. SQL 作为查询接口——SQL 仍然是数据查询的标准,NoSQL 最终也被迫加了 SQL-like 的接口(Cassandra 的 CQL、MongoDB 的聚合框架)。NewSQL 干脆从 SQL 出发。
  2. 水平扩展——像 NoSQL 一样自动分片、自动再平衡。
  3. 强 ACID 事务——不妥协。不像 NoSQL 那样放弃事务。

NewSQL 的代表

系统定位核心设计
CockroachDB全球分布式 SQLRaft + HLC + LSM-Tree
Google Spanner超大分布式 SQLPaxos + TrueTime + Colossus
TiDB分布式 HTAPRaft + 行存/列存双引擎
VoltDB内存分布式 SQL单线程分区 + 存储过程
YugabyteDB兼容 PostgreSQL 分布式基于 Raft + DocDB(LSM)

需要注意:NewSQL 和 NoSQL 不是对立关系。NoSQL 在过去十年把"水平扩展"的技术做实了。NoSQL 证明了分布式数据存储的可行性。NewSQL 说——"好,现在把 SQL 和事务请回来。"


深入冒险:分布式事务的代价

分布式数据库的事务不是免费的。一个跨 Region 的事务需要:

  1. 协调器收到 Begin Transaction
  2. Write 写入所有参与的数据分片(跨机器、跨机房)
  3. Prepare 阶段——两阶段提交:所有参与节点准备就绪?
  4. Commit 阶段——协调器决定 commit,所有节点执行 commit 或 abort

两步的代价

  • 延迟:跨 Region 的每个 RTT 多 100ms+。一个 2PC(两阶段提交)至少 3 次 RTT。
  • 锁:Prepare 阶段锁住数据,直到全部 commit 或 abort。锁的时间越久,并发争用越严重。
  • 风险:协调器在 Prepare 后挂了,所有参与节点进入 in-doubt 状态——等恢复。

这就是为什么很多分布式数据库选择"尽量走单 Region 事务,跨 Region 尽少"。CockroachDB 做事务时优先尝试在同一个 Range 内完成——如果能做,就只做一次 Raft 共识。只有跨 Range 的事务才走分布式协调。


常见陷阱

  1. "一致性哈希完全解决了数据倾斜":一致性哈希比取模均匀,但热点仍会出现。一件稀世宝物被高频查阅——即使它落在正常的环段上,这台机器也会被请求压垮。需要额外机制:负载感知的热点迁移。

  2. "分布式数据库像单机一样用":Ping 延迟是你躲不掉的。跨 Region 的 2PC 往返可能 300ms+。分布式数据库在本地机房可以达到毫秒级延迟,但全球多活必然有代价。

  3. "Raft 等于数据不丢":Raft 保证日志复制到大多数节点,但写入后在提交前 Leader 崩溃了——数据还没复制到大多数节点,丢失了。Raft 保证的是共识,不是持久性(持久性靠写入磁盘,磁盘也可能坏)。

  4. "NewSQL 完全替代 NoSQL":NewSQL 的分片粒度比 NoSQL 粗。Cassandra 的每节点写入吞吐可能比 CockroachDB 更高。NoSQL 放弃的东西——事务、SQL——换来的是更少的协调开销。

  5. "Spanner 的 TrueTime 是同步时钟":不是。TrueTime 是带误差范围的时钟,e 在 1-7ms。Spanner 的正确性不依赖于"所有节点时间完全一致"——它依赖于"知道误差范围",然后用这个误差范围决定等待时间。


通关挑战

  • ** 热身**:解释一致性哈希为什么加机器只需要迁移少量数据。
  • ** 挑战**:Raft 集群有 5 个节点。写一个模拟:Leader 挂了(进程消失),集群怎样选出新 Leader?从 Follower 的选举超时开始,到新 Leader 开始接受写请求。画出完整流程。
  • ** 观察**:启动一个 etcd(用 Docker docker run -d --name etcd quay.io/coreos/etcd),用 etcdctl put / etcdctl get 测试。停掉一个节点看看集群是否还可用。

验收标准

读完后你能:

  • 说出单机数据库在哪些方面不够用(4 面墙)
  • 解释范围分片、哈希分片、一致性哈希的区别和优劣
  • 描述 Raft 选举和日志复制的基本流程
  • 比较 CockroachDB 和 Spanner 的架构差异
  • 理解 NewSQL "SQL + 水平扩展 + ACID" 三合一的含义
  • 知道分布式事务的代价和优化方向

常见卡点

  • 一致性哈希的"顺时针找机器":不理解为什么顺时针找。你就把环想象成一个圆形的钟表,key 落在某个点上,第一个遇到的"表盘标记"就是放的机器。
  • Raft 的随机超时为什么能避免无限选举:每个 Follower 的选举超时是随机的(150-300ms)。超时最短的 Follower 变成 Candidate,在其他人超时之前发起 RequestVote——大概率只收到一个 Candidate 的投票请求,一次选出 Leader。
  • TrueTime 的 e 到底怎么用:事务 T1 得到 timestamp [t-e t+e],Spanner 在提交前等待 t+e 时间——确保所有之前的提交都能被看到。这就是 Spanner 达成外部一致性的关键。

现在不需要理解

  • Paxos 的详细协议(比 Raft 复杂,单你真正需要时去读原文即可)
  • Multi-Paxos 和 Raft 的完整对比
  • Spanner 论文中 TrueTime Wait 的严格推导
  • TiDB 的 PD 调度器和 Region 再平衡策略

旅人笔记

分布式数据库是工程上的最大赌注之一:让全球的数据像在一台机器上一样被读、写、查。 一致性哈希解决分片,Raft 解决共识,HLC/TrueTime 解决时钟。没有银弹——局部事务比跨 Region 事务快 100 倍,正常延迟下的性能和容错开销一直都在。

下一站预告

Built with VitePress | Software Systems Atlas