复制与分片
单机到头了怎么办?复制(多副本,扩展读与容错)与分片(拆数据,扩展写与容量)——每个"扩展"都以一致性或复杂性为代价。本章讲主从复制、多主复制、分片键选择与跨分片事务(→ 复制与一致性模型)。
一主多从:主处理写、复制日志,从处理读——扩展读与容错(主挂了提升一个从)。
异步复制(默认)下从库落后主库——复制延迟导致"读到旧数据":
- 读己之写(read-your-writes)被破坏:用户刚发的帖子刷新看不到——对策:关键读走主库、会话粘住主库(sticky session);
- 单调读被破坏:刷新一次看到新数据、下一次又看到旧数据(落到落后的从库)——对策:会话粘住同一个从库。
复制延迟是异步复制的固有代价,不是 bug——同步复制消延迟但每写等全部副本(主挂了就不能写,可用性换一致性,→ 故障模型与 CAP)。
主挂了提升从为主——**脑裂(split-brain)**风险:旧主复活时两个主同时写。对策:多数派确认(新主要得到多数副本投票)、fencing token(→ 协调服务与实践模式)。自动故障切换的正确性靠共识协议(Raft 组),手动切换靠流程。
多个主都能写(跨地域部署的延迟需求),冲突由此而来:
- 写冲突:两个主同时改同一行——需要冲突解决(时间戳最后写胜、CRDT、应用层合并);
- **最后写胜(LWW)**丢弃并发写之一——语义上"丢失更新";
- MySQL Group Replication、CockroachDB、Cassandra(多主风格)各有取舍。
多主的真正用武之地是跨地域写:每个机房本地写(低延迟),后台复制——以冲突解决为代价换本地延迟。
数据按分片键拆到多台机器——扩展写与容量:
分片键的选择
Section titled “分片键的选择”分片键选错几乎无法事后修复(迁数据是全表级的痛苦)。判断标准:
- 查询模式:查询都带分片键(如 user_id)→ 点查落到单分片,高效;不带 → 广播所有分片(scatter/gather);
- 热点:分片键分布均匀(自增 ID 均匀但可能泄露顺序;hash 均匀)——热点(大 V 用户、单一租户)压垮单分片;
- 范围 vs 哈希:范围分片支持范围查询但易热点,哈希分片均匀但不支持范围——混合(时间 + 哈希)是常见折中。
跨分片的事务需要两阶段提交(2PC):协调者先问所有参与者"能提交吗"(prepare),全 yes 再提交(commit)。
- 2PC 的代价:协调者是单点——prepare 后协调者挂了,参与者锁着资源等(阻塞);网络分区时可能不一致;
- 2PC 的可用性代价:一个参与者不可用,事务就卡住——跨分片事务的扩展性差是分布式数据库(NewSQL:Spanner、CockroachDB、TiDB)的核心难题,它们的解法是 Paxos 组 + 分布式 2PC;
- 能避免就避免:按分片键设计事务(一个用户的数据在一个分片)是应用层的第一原则。
扩展的代价总结
Section titled “扩展的代价总结”| 手段 | 扩展什么 | 代价 |
|---|---|---|
| 主从复制 | 读、容错 | 复制延迟(读到旧数据) |
| 多主复制 | 本地写延迟 | 冲突解决 |
| 同步复制 | 一致性 | 写延迟、可用性 |
| 分片 | 写、容量 | 广播查询、跨分片事务(2PC) |
| 2PC | 跨分片原子性 | 协调者单点、阻塞 |
- 复制延迟导致"读到旧数据":异步复制的固有代价;关键读走主库
- 脑裂靠多数派 + fencing token 防御
- 多主以冲突解决为代价换本地写延迟;LWW 丢弃并发写
- 分片键选错几乎无法事后修复:查询模式、热点、范围 vs 哈希
- 2PC 的协调者是单点:跨分片事务能避免就避免(按分片键设计)