数据分片
分片(Sharding)把一份逻辑数据集分布到多台机器,目的是扩展容量或分担负载。它不只是路由公式,还会改变事务、查询、迁移和运维的边界。
分区、分片与复制的区别
单实例表分区(Partitioning)把表拆成较小的管理单元,可以帮助分区裁剪、归档和维护,但不一定增加机器。分片通常把子集放到不同机器。复制(Replication)给同一份数据增加副本,主要服务于容错或特定读取扩展。
三者可以同时存在:每个分片有副本,每个数据库内部也可以分区。不能因为增加了读副本,就认为单个写入主节点的热点已经被解决。
判断是否值得承担分布式复杂度
观察限制来自存储、IO、CPU、写日志、锁竞争,还是单个热点。查询和索引调整、连接池、批处理、纵向扩容、缓存或读副本可能已经足够。
只有当目标负载、增长、恢复时间或地域约束确实超出单机方案时,再引入分片。某个固定 GB 或 QPS 数值不是适用于所有数据库的分片门槛。容量之外,备份、恢复和迁移时间也可能是约束。
先选键,再选分布规则
分片键(Shard Key)决定数据的归属单元。好的键既能分散数据量与访问量,也能让常用操作落在少数分片。键的基数(Cardinality)很高不代表流量均匀;某个实验仍可能独占大部分写入。
| 规则 | 优点 | 代价 |
|---|---|---|
| 范围(Range) | 范围查询和相邻数据管理直观。 | 单调增长的时间或编号可能把新写入集中到末端。 |
| 哈希(Hash) | 通常更容易分散大量不同键。 | 失去范围局部性,扩容方式需要额外设计。 |
| 目录(Directory) | 显式映射可以迁移大租户并调整放置。 | 路由元数据需要高可用、缓存和版本管理。 |
AWS 的分区键设计说明强调活动量分布,而不只是记录数量。
热点需要改变负载单位
如果 experiment_id=42 贡献了一半写流量,对这个编号再做均匀哈希也只会得到一个结果。可把日志分成 (experiment_id, time_bucket, subshard),让一个大实验拥有多个可独立路由的写入单元。
代价是查询一个实验时需要读取多个分片并合并。子分片数量、时间桶边界和路由元数据应能被查询端发现;盲目加随机后缀却没有读取计划,会让写入变快、读取失控。
跨分片读取与事务
跨分片查询需要扇出(Fan-out)和结果聚合。全局最近 20 条可以让各分片返回局部候选再归并,但深分页、过滤和某个分片超时会使语义更复杂。二级索引可以另建访问路径,却需要讨论更新延迟与一致性。
跨分片事务可能使用分布式提交协议;也可以重新安排数据归属,使关键不变量留在一个事务边界内。Saga 等补偿流程适合可撤销的业务步骤,但不等价于单个原子事务。全局唯一编号也不自动保证全局业务唯一性。
在线扩容是一段迁移协议
一种迁移思路是:准备目标分片 → 搬迁存量快照 → 追赶增量变化 → 校验 → 切换路由版本 → 保留回滚窗口 → 清理旧副本。不同数据库会采用不同协议,但每一步都要明确读写由谁负责。
直接复制数据后立刻改路由,可能漏掉复制期间的新写入。没有协调规则的双写也可能只写成功一侧。迁移期间需要处理重复、乱序、墓碑(Tombstone)与旧路由请求,并通过写入版本或路由代次防止旧节点继续承诺写入。
托管分片仍然需要数据建模
数据库可以替你实现物理拆分和再平衡,但不能替你选择正确的访问单元。热点键、跨分区事务和二级索引仍然可能决定成本与性能。也不能假定所有托管数据库都使用相同的哈希环算法;应以产品文档描述其放置和路由机制。
关联与来源
学习入口:Hello Interview · Sharding(已登录核对书面章节)。迁移步骤与训练日志例子是独立的工程推导。