《Redis Cluster 数据分片与扩缩容实战》

深入 Redis Cluster 的 16384 槽位哈希分片、CRC16 与 hash tag、gossip 协议与集群状态、槽位迁移(MIGRATE)、扩缩容流程、集群限用命令与故障处理。

一、16384 槽位与哈希分片

1.1 为什么是 16384 个槽

Redis Cluster 将整个键空间划分为 16384 个哈希槽(hash slot),每个 key 通过 CRC16(key) % 16384 计算归属槽位,槽再分配到各节点。固定槽位设计避免了「一致性哈希」的虚拟节点复杂度和环迁移问题,让数据分布与迁移都可计算、可预期。

redis-cli -c -p 7000 cluster keyslot mykey
# (integer) 13820

16384 的选取权衡:槽位越多则元数据越大,越少则迁移粒度越粗。16384 既能支撑上千节点规模,又保证槽位 bitmap(约 2KB)与 gossip 消息足够小。

1.2 与一致性哈希对比

对比项Redis Cluster一致性哈希
映射方式CRC16(key) % 16384hash(key) 取环
迁移单位槽(16384 个)区间
虚拟节点无有
客户端定位CLUSTER KEYSLOT本地实现
# 查看集群槽位分布
redis-cli -p 7000 cluster slots
# 1) 1) (integer) 0      # 起始槽
#    2) (integer) 5460   # 结束槽
#    3) 1) "10.0.0.1"    # 节点地址
#       2) (integer) 7000 # 端口

1.3 key 与槽的关系

CLUSTER KEYSLOT user:1001     # 槽 A
CLUSTER KEYSLOT order:1001    # 槽 B
# 若 A != B,MULTI 同时操作这两个 key 会报 CROSSSLOT

槽位是 Cluster 的一切基础:事务、Lua 脚本、多 key 命令都要求涉及的 key 落在同一槽,否则报 CROSSSLOT Keys in request don't hash to the same slot。

二、CRC16 与 hash tag

2.1 CRC16 校验算法

Redis 使用 CRC16(XMODEM 变体)对 key 计算 16 位校验值,再对 16384 取模。CRC16 的散列特性让相邻 key 也均匀分散到不同槽位,实现节点间负载均衡。

CLUSTER KEYSLOT hello    # 0
CLUSTER KEYSLOT world    # 732
CLUSTER KEYSLOT foo      # 12182
CLUSTER KEYSLOT bar      # 5061

2.2 hash tag 语法

hash tag 让用户把某些 key 强制映射到同一槽。规则:key 中存在 {...} 大括号时,只对大括号内子串做 CRC16:

CLUSTER KEYSLOT {user:1001}:cart
CLUSTER KEYSLOT {user:1001}:profile
CLUSTER KEYSLOT {user:1001}:orders
# 三者同槽 → 可放在同一事务/Lua 中原子操作

2.3 hash tag 使用原则

场景是否用 hash tag说明
事务/Lua 多 key 原子是必须同槽
批量 MGET 同前缀 key是避免 CROSSSLOT
普通独立 key否打散更利于均衡
热 key 拆分否tag 会加剧热点

hash tag 破坏均匀分布——过度使用会让大量 key 堆积在少数槽位形成热点分片。只在确实需要同槽语义时使用,tag 粒度尽量细(如 {user:1001} 而非 {user})。

三、gossip 与集群状态

3.1 gossip 协议

Cluster 节点通过 gossip 协议交换状态:周期性地向随机节点发送 PING、接收 PONG,携带约 1/10 节点的状态信息(node id、ip:port、槽位 bitmap、flags、配置纪元等)。

redis-cli -c -p 7000 cluster info
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_known_nodes:6
# cluster_size:3
redis-cli -c -p 7000 cluster nodes
# <node-id> <ip:port>@<cport> <flags> <master-id> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot-range>

3.2 集群状态判定

集群状态条件后果
ok槽全部被覆盖,主节点正常正常读写
fail任一主节点失联或部分槽无主对应槽读写失败
redis-cli -c -p 7000 cluster info
# cluster_state:fail 时客户端访问报
(error) CLUSTERDOWN The cluster is down

3.3 节点间通信端口

bind 10.0.0.1
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
# 数据端口 7000,集群总线端口 17000(+10000),生产必须放通

集群总线端口是新手常踩的坑:安全组只开了数据端口,gossip 无法建立,节点互相标记 unreachable,集群永远 fail。

四、槽位迁移(MIGRATE)

4.1 迁移原理

槽迁移就是把一个槽(及其 key)从源节点搬到目标节点,底层用 MIGRATE 命令逐个 key 增量搬运、不阻塞服务:

MIGRATE 10.0.0.2 7001 "" 0 5000 KEYS mykey
# 参数: 目标ip 目标port "" 超时(ms) 重试(ms) KEYS <key...>

迁移过程中源节点把槽标记为 migrating,目标节点标记为 importing;key 迁完源节点删除本地副本,全部迁完后槽位所有权移交目标节点。

4.2 三态查找

# 1. key 在源节点 → 直接返回
GET mykey   # OK

# 2. key 已迁走 → 源节点返回 ASK 重定向
(error) ASK 3999 10.0.0.2:7001
# 客户端需先发 ASKING 再重试(redis-cli -c 自动处理)

# 3. key 不存在 → 返回 nil

4.3 迁移性能与安全

# 官方推荐的批量迁移
redis-cli -p 7000 cluster reshard --from 10.0.0.1:7000 \
  --to 10.0.0.2:7001 --slots 100 --yes
因素影响建议
key 数量迁移时长低峰迁移
网络带宽迁移速度内网执行
大 key单 key 迁移阻塞迁移前先治理

迁移是 MIGRATE 逐个 key 的串行操作。大 key(如数百万元素的 list/hash)迁移极慢且阻塞,生产迁移前应先治理大 key。

五、扩容流程

5.1 添加新节点

# 1. 启动新节点(cluster-enabled yes)
redis-server /etc/redis/redis.conf --port 7004

# 2. 加入集群
redis-cli -p 7000 cluster meet 10.0.0.4 7004
# OK

新节点加入后处于 no slots 状态,需分配槽位才能服务。

5.2 分配槽位

# 方式一:reshard 交互式迁移(推荐)
redis-cli -p 7000 cluster reshard

# 方式二:指定迁移来源与槽数
redis-cli -p 7000 cluster reshard --from 10.0.0.1:7000 \
  --to 10.0.0.4:7004 --slots 1000 --yes

5.3 扩容后的平衡

扩容把部分槽搬去新节点,key 分布天然按槽重新平衡,无需额外 rehash。用 dbsize 验证各节点均衡即可。

扩容平滑的关键:迁移尽量在业务低峰进行。迁移期间被访问的 key 会短暂经历 ASK 重定向,客户端需正确处理。

六、缩容流程

6.1 下线前迁空

# 把 7004 的槽全部迁回其他节点
redis-cli -p 7000 cluster reshard --from 10.0.0.4:7004 \
  --to 10.0.0.1:7000 --slots 16384 --yes   # slots 填该节点全部槽数

6.2 节点退出

# 槽迁空后,其他主节点 forget 该节点
redis-cli -p 7000 cluster forget <node-id-7004>

# 关闭节点进程
redis-cli -p 7004 shutdown nosave

# 确认集群恢复
redis-cli -c -p 7000 cluster info
# cluster_state:ok, cluster_known_nodes:5

6.3 缩容注意事项

注意点说明
必须迁空槽有槽节点被 forget 会丢槽
forget 需在多数节点执行否则 gossip 仍认识它
从节点随主下线先 forget 从节点
迁移期间避免 failover与故障处理叠加易混乱

缩容失败最常见原因:槽未迁空就 forget,导致集群 fail。务必用 cluster nodes 确认目标节点 slot 为空再做 forget。

七、集群限用命令

7.1 多 key 命令限制

# 跨槽报错示例
MGET user:1 user:2        # 若不同槽 → CROSSSLOT
MSET a 1 b 2              # 同上
RENAME key1 key2          # 两 key 必须同槽
SMOVE set1 set2 member    # 两集合必须同槽

# 解决:hash tag 或拆分执行
MSET {user:1}:name tom {user:1}:age 18

7.2 受限或禁用命令

命令原因替代
KEYS全节点扫描性能差SCAN + hash tag
FLUSHALL需全部节点执行逐节点 FLUSHDB
MOVE槽机制取代CLUSTER SETSLOT
跨槽 MGET/MSETCROSSSLOThash tag 或拆分
RANDOMKEY跨节点随机应用层控制
redis-cli -c -p 7000 --scan --pattern "user:*"

Cluster 下很多单机习惯要改:批量操作收敛到同槽,全量扫描用 SCAN 分片执行。开发阶段就应开启 Cluster 环境测试,避免上线后才发现 CROSSSLOT。

八、故障处理

8.1 主从切换

每个主节点建议配 1~2 个从节点。主节点在 cluster-node-timeout 内失联,其余主节点投票选出新主(从节点晋升):

# 主节点故障后从节点自动升级;手动故障转移(演练):
redis-cli -c -p 7001 cluster failover

8.2 槽覆盖与部分失败

Cluster 按槽隔离故障:只有故障节点负责的槽受影响,其他槽继续服务:

# 访问故障节点所属槽
(error) CLUSTERDOWN Hash slot not served
# 访问其他槽 → 正常

8.3 故障演练与恢复

# 演练:kill 一个主节点
redis-cli -p 7000 shutdown nosave

# 其从节点自动晋升
redis-cli -c -p 7001 cluster nodes

# 恢复:原主重启后以从节点身份自动回归
redis-server /etc/redis/redis.conf --port 7000
故障场景影响处理
从节点宕机冗余降低重启或新增
主节点宕机且有从该槽短暂不可用后切换观察新主数据完整性
主节点宕机无从槽无主集群 fail紧急新增节点
网络分区可能脑裂靠 cluster-node-timeout 收敛

cluster-node-timeout 决定故障判定速度,默认 15 秒。过小会因抖动频繁切换,过大延长不可用窗口。生产建议 10~20 秒并配合监控。

九、集群监控与最佳实践

9.1 核心监控指标

指标正常值告警
cluster_stateokfail 立即告警
cluster_known_nodes与配置一致变化告警
cluster_slots_ok16384<16384 告警
节点内存<maxmemory>80% 告警
槽迁移进行中无有迁移告警
# 采集命令
redis-cli -c -p 7000 cluster info
redis-cli -c -p 7000 cluster nodes
redis-cli -c -p 7000 info cluster

9.2 最佳实践清单

# 某些槽无主时允许其他槽继续服务(权衡取舍)
cluster-require-full-coverage no
实践说明
每个主节点配从节点保障切换能力
hash tag 克制使用避免热点分片
批量操作收敛同槽避免 CROSSSLOT
大 key 治理迁移与同步不受拖累
低峰期扩缩容减少业务影响
定期演练 failover验证切换链路

9.3 从单机迁移到 Cluster

单机数据迁移推荐 RedisShake(全量快照 + 增量同步 + 校验)。

上 Cluster 前先评估:数据总量超过单机内存,或写吞吐超过单机才值得。读多写少、数据量小用 Sentinel 更简单。

结语

  1. Redis Cluster 用 16384 个哈希槽分片,CRC16(key) % 16384 决定归属,槽位是事务、脚本、多 key 命令的前提。
  2. hash tag 用 {...} 强制同槽满足原子操作,但过度使用会制造热点分片,必须克制。
  3. gossip 协议让节点自组织交换状态,集群总线端口(+10000)必须放通,否则集群永远 fail。
  4. 槽迁移基于 MIGRATE 逐个 key 增量搬运,不阻塞服务,但大 key 会拖慢迁移,需先治理。
  5. 扩容用 cluster meet + cluster reshard 平滑搬槽;缩容必须先迁空槽再 cluster forget。
  6. Cluster 限用跨槽多 key 命令,批量操作用 hash tag 或拆分,全量扫描用 SCAN。
  7. 故障按槽隔离:主节点宕机由从节点自动晋升,无主槽不可用但其余槽照常服务。
  8. 监控聚焦 cluster_state、槽覆盖、节点内存与迁移活动,配合定期 failover 演练保障生产稳定。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「redis」更多文章

  1. 《Redis 容灾与备份恢复:RDB/AOF 备份、复制与演练》
  2. 《Redis 对象编码与内存优化:listpack 与编码转型》
  3. 《Redis 管道、事务与批量优化:从 N 次 RTT 到一次》