1. CAP 定理的深层含义
CAP 定理指出分布式系统无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)。
C(一致性)
/\
/ \
/ \
/ CAP \
/ 三角 \
/__________\
A(可用性) P(分区容错)
现代工程实践认为 P 是必须接受的——网络分区是物理现实,因此设计决策落在 C 与 A 之间的权衡:
- CP 系统:分区时拒绝写入,保证数据一致(ZooKeeper、etcd、Consul)。
- AP 系统:分区时继续服务,允许短暂不一致(Cassandra、DynamoDB)。
PACELC 定理进一步细化:即使无分区,也存在延迟(Latency)与一致性(Consistency)的取舍。
2. BASE 与 ACID 的博弈
ACID 是单机数据库的基石,但分布式环境下全序事务成本极高。BASE 提供另一种思路:
| 维度 | ACID | BASE |
|---|---|---|
| 一致性 | 强一致性 | 最终一致性 |
| 隔离 | 事务隔离 | 无全局隔离 |
| 可用性 | 事务阻塞 | 优先可用 |
| 适用场景 | 金融核心交易 | 电商库存、社交 feed |
BASE 并不代表放弃一致性,而是通过异步补偿、幂等重试和版本向量等机制,将一致性收敛到可接受的时间窗口内。
3. Google Spanner
Spanner 是 Google 的全球分布式 NewSQL 数据库,核心突破在于通过 TrueTime API 实现外部一致性(External Consistency),即事务提交顺序严格等同于真实物理时间顺序。
3.1 TrueTime API
TrueTime 时间区间:[earliest, latest]
earliest = TT.now().earliest
latest = TT.now().latest
提交协议:
1. 获取 commit_timestamp = TT.now().latest
2. 等待直至 TT.now().earliest > commit_timestamp
3. 正式提交,保证后续读取必见此事务
TrueTime 将误差界(epsilon)控制在毫秒级,每个时间戳不是单点值而是一个时间区间。提交时通过等待误差界消逝,确保所有节点在提交后的一致性读都能观察到该事务。
3.2 事务架构
┌─────────────┐ ┌─────────────┐
│ Client │────>│ Spanner │
└─────────────┘ │ Gateway │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Paxos G1 │ │ Paxos G2 │ │ Paxos G3 │
│ - Shard A│ │ - Shard B│ │ - Shard C│
│ - Leader │ │ - Leader │ │ - Leader │
└──────────┘ └──────────┘ └──────────┘
Spanner 以目录(Directory)为单位分片,每个分片对应一个 Paxos 复制组。单分片写事务直接通过 Paxos Leader 提交;跨分片写事务使用两阶段提交(2PC),协调者将事务日志同样写入 Paxos 组保证容错。
4. TiDB 架构解析
TiDB 是 PingCAP 推出的开源分布式 HTAP 数据库,兼容 MySQL 协议,采用计算存储分离架构。
4.1 三层架构
┌─────────────────────────────────────────────┐
│ TiDB Server (SQL 层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ SQL │ │ DDL │ │ Parser │ │
│ │ Plan │ │ Worker │ │ Optimizer│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ 无状态,可水平扩展 │
└────────────────────┬────────────────────────┘
│ gRPC / TiKV Client
┌────────────────────┴────────────────────────┐
│ TiKV (分布式事务存储层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Region │ │ Raft │ │ RocksDB │ │
│ │ Leader │ │ Log │ │ Engine │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ 数据分片为 Region,默认 96MB,Raft 复制 │
└─────────────────────────┬───────────────────┘
│
┌─────────────────────────┴───────────────────┐
│ PD (Placement Driver) │
│ - 全局时间戳分配(TSO) │
│ - Region 调度与负载均衡 │
│ - 元数据管理与高可用切换 │
└─────────────────────────────────────────────┘
4.2 TiKV 存储引擎
TiKV 将数据组织为 Key-Value 形式,底层使用 RocksDB。每张表的每行数据被编码为形如 t{tableID}_r{rowID} 的 Key,索引则编码为 t{tableID}_i{indexID} 前缀的二分搜索结构。
跨区域事务通过 Percolator 模型实现:预写(Prewrite)阶段在主键上锁并写入数据,提交(Commit)阶段向 PD 申请全局单调递增时间戳并解锁。此模型将分布式事务冲突检测下沉到存储层,使 TiDB Server 保持无状态。
4.3 TSO 时间戳
PD 作为全局时钟源分配时间戳:
timestamp = (physical_time_ms << 18) | logical_counter
单次申请批量预分配(默认 3000 个),降低网络 RTT 开销,保证事务提交顺序与全局时钟严格递增。
5. CockroachDB 串行化
CockroachDB 是另一款受 Spanner 启发的开源分布式数据库,其核心设计目标为默认串行化隔离级别。
CockroachDB 采用混合逻辑时钟(HLC)替代物理时钟,结合无主多活的共识组设计。事务引擎使用 Serializable Snapshot Isolation(SSI),通过以下机制检测冲突:
- 读时间戳跟踪:事务读取某 Key 时记录时间戳,提交时校验该 Key 是否在此时间戳之后被修改。
- 写意图(Write Intents):事务写入时留下意向标记,后续事务遇到意向需等待或重试。
- 时间戳推送:当检测到低优先级冲突时,系统将事务时间戳向前推进而非直接回滚,减少重试率。
-- CockroachDB 默认即串行化
BEGIN;
SELECT balance FROM accounts WHERE id = 'A';
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT; -- 若冲突自动重试(由驱动层隐藏)
6. 分布式事务模式
6.1 两阶段提交 (2PC)
Coordinator Participant A Participant B
│ PREPARE │ │
│ ──────────────────────> │ │
│ PREPARE │ │
│ ──────────────────────────────────────────────> │
│ YES │ │
│ <────────────────────── │ │
│ YES │ │
│ <────────────────────────────────────────────── │
│ COMMIT │ │
│ ──────────────────────> │ │
│ COMMIT │ │
│ ──────────────────────────────────────────────> │
2PC 保证原子性,但协调者宕机可能导致参与者长时间阻塞。工业界常在协调者日志中加入事务状态以支持恢复。
6.2 Saga 模式
将长事务拆分为一系列本地事务,每个本地事务提交后发送事件触发下一个。失败时执行补偿操作:
T1: 扣减库存 ──success──> T2: 创建订单 ──success──> T3: 扣款
fail <── Compensate T1 <── fail
Saga 适合高并发长流程业务,牺牲瞬时一致性以换取吞吐量和可用性。编排方式分为编排式(Orchestration)和协同式(Choreography)两类。
6.3 TCC 与 AT
TCC(Try-Confirm-Cancel)要求业务方预留资源:
| 阶段 | 操作 |
|---|---|
| Try | 检查并预留资源(如冻结金额) |
| Confirm | 确认执行,将预留转为正式 |
| Cancel | 取消预留,回滚到初始状态 |
AT(Automatic Transaction)由 Seata 等框架实现,通过解析业务 SQL 自动生成反向补偿 SQL,对业务侵入更小。
7. 分片策略
分片(Sharding)将数据水平拆分到多个节点,常见策略如下:
| 策略 | 路由规则 | 优点 | 缺点 |
|---|---|---|---|
| Hash | hash(user_id) % N | 数据均匀、避免热点 | 扩容需全量重分布 |
| Range | user_id [0, 1M) | 范围查询友好、扩容平滑 | 单调写入易形成热点 |
| List | region IN (‘CN’, ‘US’) | 按业务语义分组 | 需提前规划枚举值 |
| Composite | Hash + Range 分段 | 兼顾均匀与查询 | 路由逻辑复杂 |
一致性哈希(Consistent Hashing)可显著降低 Hash 分片扩容时的数据迁移量:引入虚拟槽位后,新增节点仅需负责部分槽位的迁移,而非全表重分布。
8. 全局二级索引
分片数据库中,主键索引天然分片,但二级索引若按索引 Key 独立分片则需处理分布式事务一致性问题。工程上通常采用以下方案:
- 本地索引:二级索引与主表同分片,写入原子但跨分片查询需广播。
- 全局索引:独立分片存储,查询仅需一次定位,但写入涉及主表与索引表的原子更新(如 TiDB 的 Global Index 通过异步 TTL 校验保证最终一致)。
- 复制表:小维度表全量复制到所有节点,消除跨分片 JOIN(如 TiDB 的 TiFlash 小表广播)。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。