数据分片策略:水平分片、垂直分片、一致性哈希、虚拟节点

数据分片,说白了就是把一个大库拆成多个小库。你想想看,单库扛不住千万级流量,这是早晚的事。我早年做过一个电商项目,订单表到了 5000 万行,一个简单的分页查询能跑 3 秒多——嗯,从那以后我就彻底倒向了分片架构。

这一章我们聊四种主流策略:水平分片、垂直分片、一致性哈希、虚拟节点。每种都有它的适用场景和坑,我一个个说。

水平分片(Horizontal Sharding)

水平分片是按行拆分。比如用户表有 1 亿行,按 user_id 取模,分到 16 个库里去。每个库的表结构完全一样,只是数据范围不同。

核心公式: shard_id = hash(key) % N

我在项目中遇到过一个问题:取模分片在扩容时特别痛苦。原来 16 个库,要扩到 32 个,所有数据都得重新计算路由。那段时间我们团队通宵了好几次做数据迁移。

水平分片的优缺点很鲜明:

维度 说明
优点 扩展性好,写压力分散均匀,适合大数据量场景
缺点 跨分片查询复杂,扩容需要数据重分布
适用场景 用户表、订单表、日志表等海量数据场景
避坑指南: 我曾经因为分片键选错导致热点问题。选分片键时一定要考虑业务访问模式,别选那种有明显倾斜的字段(比如性别、地区)。

垂直分片(Vertical Sharding)

垂直分片是按列拆分。把一张宽表拆成多张窄表,每张表只存部分字段。比如把用户表拆成「用户基本信息表」和「用户扩展信息表」。

为什么要这么做?我举个例子。用户表有 50 个字段,但业务上 80% 的查询只用到其中 10 个字段。剩下的 40 个字段(比如收货地址、偏好设置)很少被访问。把它们拆出去,能显著减少 IO 和缓存浪费。

垂直分片通常和水平分片配合使用。先垂直拆分,再对核心表做水平拆分。这是大型系统的常见套路。

对比项 水平分片 垂直分片
拆分维度 按行 按列
表结构 完全相同 不同
主要目的 解决数据量过大 解决字段过多、访问不均衡
扩容难度 高(需重分布) 低(加表即可)

一致性哈希(Consistent Hashing)

取模分片最大的痛点是扩容。一致性哈希就是来解决这个问题的。

它的原理很简单:把哈希值空间组织成一个环(0 到 2^32-1)。每个节点在环上占据一个位置。数据根据哈希值落在环上,顺时针找到第一个节点。

扩容时,只影响相邻节点的数据。比如原来有 3 个节点,加 1 个节点,只需要迁移大约 1/4 的数据。而取模分片要迁移 3/4。

个人经验: 我建议在缓存场景优先使用一致性哈希。比如 Redis 集群,用一致性哈希做分片,加节点时缓存命中率下降可控。我在一个日活千万的 feed 流系统里用过,效果不错。

一致性哈希也有缺点:节点少时容易分布不均。比如只有 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();
    }
}
注意: 虚拟节点虽然解决了分布均匀问题,但增加了路由表大小。我曾经在项目中把虚拟节点数设到 1000,结果内存占用飙升。建议根据节点数和数据量做权衡。

四种策略对比

策略 扩容难度 数据均匀性 跨分片查询 典型场景
水平分片 高 好 困难 海量数据存储
垂直分片 低 一般 中等 宽表拆分
一致性哈希 低 较好 困难 缓存、分布式存储
虚拟节点 低 好 困难 大规模集群

数据分片策略总览图

数据分片策略总览 数据分片策略 水平分片 垂直分片 一致性哈希 虚拟节点 特性 • 按行拆分 • 表结构相同 • 扩容需重分布 特性 • 按列拆分 • 表结构不同 • 适合宽表场景 特性 • 哈希环结构 • 扩容影响小 • 适合缓存场景 特性 • 物理→虚拟映射 • 分布更均匀 • 管理开销略大 选择建议 数据量大 → 水平分片 + 虚拟节点 字段多 → 先垂直拆分,再水平拆分

实际项目中的选择建议

我做了这么多年分布式系统,总结了几条经验:

  • 数据量在千万级以下: 别急着分片。先做读写分离、缓存优化,很多时候够用。
  • 数据量在亿级: 水平分片 + 一致性哈希是主流方案。我建议用虚拟节点来保证均匀性。
  • 字段超过 30 个且访问不均衡: 先做垂直拆分。把高频字段和低频字段分开。
  • 需要频繁扩容: 一致性哈希 + 虚拟节点是最优解。取模分片会让你痛不欲生。
我的习惯: 新项目我一般先用一致性哈希 + 虚拟节点做分片。虽然初期代码复杂一点,但后期扩容时你会感谢这个选择。我曾经在一个项目里用取模分片,半年内扩了 3 次容,每次都是通宵迁移——那滋味,不想再体验第二次。

好了,数据分片策略就聊到这里。记住一句话:没有银弹,只有最适合你业务场景的方案。选型时多想想未来半年的数据增长,别只看眼前。