什么是 Redis 的数据分片?

#中间件#面试八股

什么是 Redis 的数据分片?

结论:数据分片(Sharding)指把一份逻辑上的数据按规则拆到多个 Redis 实例上,从而突破单机的内存与吞吐上限。四种经典规则——范围分片、哈希取模、一致性哈希、虚拟槽——Redis Cluster 选了虚拟槽(16384 个槽),因为它在扩缩容时的数据迁移量与路由成本之间最平衡。分片带来的代价是:跨分片操作受限、事务与 Lua 必须同槽、运维复杂度上升。

一、四种分片规则对比

规则路由方式扩容影响优点缺点
范围分片按 ID/时间区间分配只需迁移边界数据支持范围查询、易理解热点集中(如新数据全在最后一个分片)
哈希取模hash(key) % NN 变化几乎全量重分布分布均匀、实现简单扩缩容代价极高,需停机或双写迁移
一致性哈希哈希环 + 虚拟节点只影响相邻节点扩缩容迁移量小实现复杂、仍可能倾斜
虚拟槽(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 操作与跨分片聚合就成为必须提前设计的约束,而不是事后补救。
笔记加载中…