什么是 Redis 的数据分片?
结论:数据分片(Sharding)指把一份逻辑上的数据按规则拆到多个 Redis 实例上,从而突破单机的内存与吞吐上限。四种经典规则——范围分片、哈希取模、一致性哈希、虚拟槽——Redis Cluster 选了虚拟槽(16384 个槽),因为它在扩缩容时的数据迁移量与路由成本之间最平衡。分片带来的代价是:跨分片操作受限、事务与 Lua 必须同槽、运维复杂度上升。
一、四种分片规则对比
| 规则 | 路由方式 | 扩容影响 | 优点 | 缺点 |
|---|---|---|---|---|
| 范围分片 | 按 ID/时间区间分配 | 只需迁移边界数据 | 支持范围查询、易理解 | 热点集中(如新数据全在最后一个分片) |
| 哈希取模 | hash(key) % N | N 变化几乎全量重分布 | 分布均匀、实现简单 | 扩缩容代价极高,需停机或双写迁移 |
| 一致性哈希 | 哈希环 + 虚拟节点 | 只影响相邻节点 | 扩缩容迁移量小 | 实现复杂、仍可能倾斜 |
| 虚拟槽(Cluster) | CRC16(key) % 16384,槽→节点 | 以槽为单位迁移 | 迁移粒度可控、支持在线扩缩容 | 需要客户端/代理理解槽路由 |
二、三种部署形态
① 客户端分片 ② 代理分片 ③ 服务端分片(Cluster)
┌──────────┐ ┌──────────┐ ┌─────────────────────┐
│ 业务应用 │ │ 业务应用 │ │ 业务应用 │
│ 自带路由 │ │ 连代理 │ │ 连任意节点 │
└────┬─────┘ └────┬─────┘ └──────┬──────────────┘
├──► Redis A ▼ ▼
├──► Redis B ┌──────────┐ ┌───────────────┐
└──► Redis C │ 代理层 │ │ 节点间 gossip │
│(Codis/ │ │ 自管槽与迁移 │
│ Twemproxy)│ └───────────────┘
└────┬─────┘
├──► Redis A/B/C
- 客户端分片:Jedis 的
ShardedJedis、早期业务自研路由。零额外组件,但扩容要改客户端逻辑,多语言栈难统一。 - 代理分片:Twemproxy、Codis、Predis 集群代理。业务无感,但多一跳延迟,代理本身要保证高可用。
- 服务端分片:Redis Cluster 原生支持,节点间 gossip 同步槽映射,客户端按 MOVED/ASK 重定向。
三、扩容与迁移
# Cluster:以槽为单位在线迁移,源节点与目标节点配合
redis-cli --cluster reshard 10.0.0.1:6379 \
--cluster-from <source-node-id> --cluster-to <target-node-id> \
--cluster-slots 1000 --cluster-yes
# 迁移过程(简化)
# 1) 目标节点 CLUSTER SETSLOT <slot> IMPORTING <source-id>
# 2) 源节点 CLUSTER SETSLOT <slot> MIGRATING <target-id>
# 3) 源节点逐个 key 执行 MIGRATE
# 4) 双方 CLUSTER SETSLOT <slot> NODE <target-id>
# 迁移期间该槽的请求:已迁走的 key 返回 ASK,未迁走的正常处理
扩容期的三个注意点:迁移会占用网络与 CPU,应在低峰进行并限制并发;大 key 会让单次 MIGRATE 变慢,迁移前先拆分;迁移中客户端要正确处理 ASK,否则会出现短暂读不到数据。
四、分片带来的限制
- 多 key 操作:
MGET、SUNION、MSET要求所有 key 同槽,否则 CROSSSLOT; - 事务与 Lua:
MULTI中的 key、脚本中的KEYS必须同槽; KEYS/SCAN:只能扫单节点,全库遍历要遍历所有主节点;- 数据倾斜:某个 key 特别大或某个前缀访问特别热,会把单个分片打满;
- 跨分片聚合:求和、排序、分页都要在应用层做,或引入汇总层。
// 用 hash tag 把同一订单相关的 key 固定到同一槽
String orderKey = "{order:1001}:detail"; // 槽由 "order:1001" 决定
String orderLock = "{order:1001}:lock";
// 两者 CRC16 输入相同 → 同槽 → 可以在一个 Lua 脚本里一起操作
// 反例:以下两个 key 可能落在不同槽,MGET 直接报错
// "order:1001:detail" 与 "order:1002:detail"
五、常见追问
- 问:为什么 Cluster 用 16384 个槽而不是按节点哈希?答:槽是"逻辑分片单元",节点增减时只需搬槽,不必重算所有 key;16384 在心跳包体积(2 KB 位图)与分散度之间取平衡。
- 问:一致性哈希比虚拟槽差在哪?答:一致性哈希的迁移粒度是"环上的区间",难以精确控制迁移量与时机;虚拟槽天然支持"一次迁 N 个槽",运维可控。
- 问:分片后还能用事务吗?答:只能在同一分片内用;跨分片需要分布式事务或改用"业务补偿",一般不建议在缓存层做跨分片事务。
- 问:哈希取模扩容为什么痛苦?答:N 从 3 变 4 时,绝大多数 key 的取模结果都变,必须全量重分布;要么停机,要么双写 + 逐步迁移。
- 问:分片数和副本数怎么定?答:按"内存上限 / 单实例建议内存(≤16 GB)"确定主节点数,副本数至少 1;总节点数要满足多数派(≥3 主),否则故障转移无法投票。
- 问:分片后监控要看什么?答:每个分片的 QPS、内存、命中率、慢查询、槽分布是否均匀;倾斜比总量更值得警惕。
小结
- 分片的本质是"用路由换容量与吞吐",规则选择决定了扩容成本。
- Redis Cluster 的虚拟槽是工程折中:迁移粒度细、路由可缓存、扩容可在线。
- 分片一旦落地,多 key 操作与跨分片聚合就成为必须提前设计的约束,而不是事后补救。