一、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) % 16384 | hash(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/MSET | CROSSSLOT | hash 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_state | ok | fail 立即告警 |
cluster_known_nodes | 与配置一致 | 变化告警 |
cluster_slots_ok | 16384 | <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 更简单。
结语
- Redis Cluster 用 16384 个哈希槽分片,
CRC16(key) % 16384决定归属,槽位是事务、脚本、多 key 命令的前提。 - hash tag 用
{...}强制同槽满足原子操作,但过度使用会制造热点分片,必须克制。 - gossip 协议让节点自组织交换状态,集群总线端口(+10000)必须放通,否则集群永远 fail。
- 槽迁移基于
MIGRATE逐个 key 增量搬运,不阻塞服务,但大 key 会拖慢迁移,需先治理。 - 扩容用
cluster meet+cluster reshard平滑搬槽;缩容必须先迁空槽再cluster forget。 - Cluster 限用跨槽多 key 命令,批量操作用 hash tag 或拆分,全量扫描用 SCAN。
- 故障按槽隔离:主节点宕机由从节点自动晋升,无主槽不可用但其余槽照常服务。
- 监控聚焦
cluster_state、槽覆盖、节点内存与迁移活动,配合定期 failover 演练保障生产稳定。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。