数据分片策略:水平分片、垂直分片、一致性哈希、虚拟节点
数据分片,说白了就是把一个大库拆成多个小库。你想想看,单库扛不住千万级流量,这是早晚的事。我早年做过一个电商项目,订单表到了 5000 万行,一个简单的分页查询能跑 3 秒多——嗯,从那以后我就彻底倒向了分片架构。
这一章我们聊四种主流策略:水平分片、垂直分片、一致性哈希、虚拟节点。每种都有它的适用场景和坑,我一个个说。
水平分片(Horizontal Sharding)
水平分片是按行拆分。比如用户表有 1 亿行,按 user_id 取模,分到 16 个库里去。每个库的表结构完全一样,只是数据范围不同。
我在项目中遇到过一个问题:取模分片在扩容时特别痛苦。原来 16 个库,要扩到 32 个,所有数据都得重新计算路由。那段时间我们团队通宵了好几次做数据迁移。
水平分片的优缺点很鲜明:
| 维度 | 说明 |
|---|---|
| 优点 | 扩展性好,写压力分散均匀,适合大数据量场景 |
| 缺点 | 跨分片查询复杂,扩容需要数据重分布 |
| 适用场景 | 用户表、订单表、日志表等海量数据场景 |
垂直分片(Vertical Sharding)
垂直分片是按列拆分。把一张宽表拆成多张窄表,每张表只存部分字段。比如把用户表拆成「用户基本信息表」和「用户扩展信息表」。
为什么要这么做?我举个例子。用户表有 50 个字段,但业务上 80% 的查询只用到其中 10 个字段。剩下的 40 个字段(比如收货地址、偏好设置)很少被访问。把它们拆出去,能显著减少 IO 和缓存浪费。
垂直分片通常和水平分片配合使用。先垂直拆分,再对核心表做水平拆分。这是大型系统的常见套路。
| 对比项 | 水平分片 | 垂直分片 |
|---|---|---|
| 拆分维度 | 按行 | 按列 |
| 表结构 | 完全相同 | 不同 |
| 主要目的 | 解决数据量过大 | 解决字段过多、访问不均衡 |
| 扩容难度 | 高(需重分布) | 低(加表即可) |
一致性哈希(Consistent Hashing)
取模分片最大的痛点是扩容。一致性哈希就是来解决这个问题的。
它的原理很简单:把哈希值空间组织成一个环(0 到 2^32-1)。每个节点在环上占据一个位置。数据根据哈希值落在环上,顺时针找到第一个节点。
扩容时,只影响相邻节点的数据。比如原来有 3 个节点,加 1 个节点,只需要迁移大约 1/4 的数据。而取模分片要迁移 3/4。
一致性哈希也有缺点:节点少时容易分布不均。比如只有 3 个节点,可能某个节点承担了 60% 的流量。怎么解决?虚拟节点。
虚拟节点(Virtual Nodes)
虚拟节点就是把一个物理节点映射成多个虚拟节点。比如 3 台物理机器,每台映射成 100 个虚拟节点,环上就有 300 个点。数据分布会更均匀。
我习惯把虚拟节点数设为物理节点数的 100~200 倍。太少效果不明显,太多管理开销大。
// 虚拟节点路由示例(伪代码)
class ConsistentHashRouter {
private final TreeMap<Integer, String> ring = new TreeMap<>();
private final int virtualNodeCount = 150;
public void addNode(String node) {
for (int i = 0; i < virtualNodeCount; i++) {
String vnode = node + "#" + i;
int hash = hash(vnode);
ring.put(hash, node);
}
}
public String getNode(String key) {
int hash = hash(key);
Map.Entry<Integer, String> entry = ring.ceilingEntry(hash);
if (entry == null) {
entry = ring.firstEntry();
}
return entry.getValue();
}
}
四种策略对比
| 策略 | 扩容难度 | 数据均匀性 | 跨分片查询 | 典型场景 |
|---|---|---|---|---|
| 水平分片 | 高 | 好 | 困难 | 海量数据存储 |
| 垂直分片 | 低 | 一般 | 中等 | 宽表拆分 |
| 一致性哈希 | 低 | 较好 | 困难 | 缓存、分布式存储 |
| 虚拟节点 | 低 | 好 | 困难 | 大规模集群 |
数据分片策略总览图
实际项目中的选择建议
我做了这么多年分布式系统,总结了几条经验:
- 数据量在千万级以下: 别急着分片。先做读写分离、缓存优化,很多时候够用。
- 数据量在亿级: 水平分片 + 一致性哈希是主流方案。我建议用虚拟节点来保证均匀性。
- 字段超过 30 个且访问不均衡: 先做垂直拆分。把高频字段和低频字段分开。
- 需要频繁扩容: 一致性哈希 + 虚拟节点是最优解。取模分片会让你痛不欲生。
好了,数据分片策略就聊到这里。记住一句话:没有银弹,只有最适合你业务场景的方案。选型时多想想未来半年的数据增长,别只看眼前。