16.1 分片、复制组与共识
本节讨论常见的自动分片数据库:把键空间切成数据单元,为每个单元放置副本,再用路由与共识组织访问。分布式数据库也可以先复制而不分片,不能把这一条实现路线当成全部定义。
先确定为什么要分布式
常见目标有四类,方案并不相同:
- 容量:单机存储、内存或 I/O 已不足;
- 吞吐:可按互不冲突的键并行扩展;
- 可用性:节点或故障域损坏后仍能服务;
- 地理延迟:让数据与主要用户或计算靠近。
仅为了高可用,单主库加同步/异步副本可能比自动分片数据库简单。仅为了分析容量,对象存储加计算集群也可能更合适。分布式 SQL 的价值在于需要跨分片事务、关系模型与自动再平衡同时成立时,愿意承担相应复杂度。
不要引用“单机只能承载几 TB”一类固定门槛。上限取决于硬件、数据温度、索引放大、维护窗口、恢复时间和 workload;真正的分片触发点应来自容量曲线与 SLO。
分片函数决定局部性与热点
Range Sharding
排序键空间被切成连续区间:
[a, g) -> shard 1
[g, n) -> shard 2
[n, z) -> shard 3范围查询只访问相邻分片,数据压缩和顺序扫描也更自然。但单调递增时间或 ID 可能把最新写集中到最后一个 range。系统可自动 split 和迁移 range,却不能消除单个逻辑键的热点。
Hash Sharding
对分区键哈希后映射到 bucket,通常能分散随机键写入:
bucket = hash(partition_key) mod bucket_count它牺牲相邻键局部性。直接对物理节点数取模还会让扩缩容时大量键重新映射,因此生产系统通常引入固定逻辑 bucket、range/slot 或一致性哈希层,将逻辑分片再映射到节点。
复合键
实际常组合租户、时间 bucket 和实体 ID:
tenant_id / month / event_id租户字段便于聚集同一租户的数据,权限隔离仍由访问控制完成;月份限制时间跨度,并不保证数据量上限,event ID 需在选定作用域内保持唯一。但大租户仍需进一步打散,小租户过度打散又会增加 fan-out。分片键是长期数据契约,不是上线前随便选一个高基数字段。
分片与副本是两条不同的轴
分片回答“哪部分数据由谁负责”,复制回答“这部分数据有几份、如何保持同序”。一个常见结构是:
range A -> replicas A1, A2, A3 -> one Raft group
range B -> replicas B1, B2, B3 -> another Raft group每个 range 通常有自己的 leader/leaseholder 和共识日志。客户端连到任意网关,并不表示任意副本都能无条件返回最新读;网关可能转发到当前负责人,陈旧读则可能在满足 closed timestamp 等条件时由 follower 本地服务。
Raft 解决复制日志顺序,不解决全部数据库问题
Raft 把客户端命令追加到复制日志。leader 可以在当前任期的一条日志被多数投票成员持久化后,推进其已提交位置并连带提交之前的前缀;不能仅凭旧任期条目已有多数副本就直接提交它,状态机按同一日志顺序应用命令。三投票副本需要两票,五副本需要三票;增加非投票副本不会自动增加容错投票数。任期、选举限制和日志匹配共同保证已提交前缀不会被另一合法 leader 改写。
它主要解决一个复制组的共识问题,不自动提供:
- 跨多个 Raft group 的原子事务;
- SQL 隔离级别与谓词冲突检测;
- 分片键选择、热点拆分和全局二级索引;
- 备份、误删恢复和跨版本升级;
- “写到多数派就一定不会丢”的硬件持久化细节。
数据库需要在 Raft 之上增加事务记录、锁/intent、MVCC 时间戳和恢复协议。
Leader、Lease 与读路径
写入通常经过当前 leader,至少等待多数投票副本确认。线性一致读需要确认当前读权限,并等待本地状态机应用到相应已提交位置;可由 ReadIndex 或有完整时钟前提的租约协议完成。仅记得“我曾是 leader”不够。例如确认可读到日志位置 105,而本地只应用到 102,就不能拿 102 的状态直接回答这次读。
Follower read 降低跨地域读延迟,但必须明确时间戳:
- 线性一致最新读可能跨区找负责人;
- 历史读可在安全时间与本地已应用进度允许的范围内读取 MVCC 版本;要称为有界陈旧,还需确认请求时间戳满足业务给定的新鲜度下限;
- 普通异步副本读若没有边界,只能描述为“可能陈旧”。
“读写分离”不是免费的扩容开关。业务必须知道哪些页面可以旧、允许旧多久,以及写后从哪里读。
再平衡的隐藏成本
节点加入后,系统需要复制 range 快照、追赶日志、转移 leader/lease,并更新路由元数据。再平衡会消耗网络、磁盘和 CPU;若多个副本同时迁移,还可能削弱故障容忍。
运维至少应监控:
- range 大小和 QPS 偏斜;
- leader/lease 分布;
- 副本欠复制与日志落后;
- split、merge、snapshot 和 rebalance 流量;
- 单键热点与跨分片 fan-out;
- 多数派丢失时哪些 range 不可用。
参考
- Raft:In Search of an Understandable Consensus Algorithm
- CockroachDB:Architecture Overview
- CockroachDB:Distribution Layer
下一节处理比复制更难的问题:一个事务同时触及多个分片时,怎样同时保证原子提交与可串行化。