MGET a b 在单机 Redis 上理所当然;在 Cluster
上却可能返回
CROSSSLOT Keys in request don't hash to the same slot——这不是客户端装傻,而是
服务端把 keyspace 切成 16384 个 hash slot
后,多 key 命令只能在 同槽
内保持原子语义。运维扩容时看到 MOVED 与
ASK
混用,若当成同一种错误,客户端路由表会被永久写错。
本文是「Redis / 缓存内核」系列第 13 篇(共 16 篇)。→ 系列目录
版本锚定:Redis OSS 7.4 / 8.x;Redis cluster specification;源码
src/cluster.c、src/cluster.h。
一、为何是 16384 slot:服务端分片 vs 客户端哈希
Memcached 与早期 Redis 代理常用 客户端一致性哈希(见 一致性哈希:Karger et al. 1997 环上虚拟节点)。Redis Cluster 选择 服务端知晓 slot 映射:每个 master 负责 slot 子集,客户端(或智能驱动)缓存 slot → 节点 表,命令先算 slot 再直连目标节点。
| 维度 | 客户端一致性哈希(典型 Memcached) | Redis Cluster hash slot |
|---|---|---|
| 分片元数据 | 客户端维护环 | 集群 CLUSTER SLOTS / 节点 gossip |
| 重分片 | 环变化 → 大量 key 迁移 | 按 slot 迁移;MOVED 更新映射 |
| 多 key 原子 | 通常 无 跨节点事务 | 同 slot 内保留 MULTI/MGET
等 |
| 槽数量 | 由虚拟节点数决定 | 固定 16384(\(2^{14}\)) |
16384 的来源在 Cluster specification:用 CRC16
对 key 哈希后取低 14 位(& 0x3FFF),在
心跳包大小 与
足够细粒度迁移 之间折中;官方建议集群规模约
千级节点 上限,而非 16384 个 master。
/* cluster.h — keyHashSlot 核心:CRC16(key) & 0x3FFF */
return crc16(key, keylen) & 0x3FFF;二、MOVED 与 ASK:永久迁移 vs 一次性询问
Cluster 节点 不代理
请求:客户端必须直连持有 slot 的 master(读可从 replica 走
READONLY + MOVED 规则,细节见官方
spec)。
stateDiagram-v2
[*] --> Stable: slot on node A
Stable --> Migrating: CLUSTER SETSLOT MIGRATING
Migrating --> Importing: dest marks IMPORTING
Importing --> Stable: migration done SETSLOT NODE
note right of Migrating
key missing on A: -ASK to B
slot owner changed: -MOVED
end note
| 重定向 | 语义 | 客户端动作 |
|---|---|---|
-MOVED <slot> <ip>:<port> |
slot 永久 归新节点 | 更新 slot 映射,重试 |
-ASK <slot> <ip>:<port> |
迁移进行中,仅下一跳 去问目标 | 向目标发
ASKING,再发原命令;不
更新永久映射 |
-TRYAGAIN |
迁移中多 key 部分存在 | 退避重试(spec:部分 key 已迁走) |
ASKING 命令在 cluster.c
中设置客户端一次性标志,使 IMPORTING
节点可服务「尚未在源节点、或已在目标」的 key;若无
ASKING,IMPORTING 节点对非本槽请求返回
MOVED,防止陈旧映射的客户端把写打到错误节点造成
双写版本。
三、hash tag 与同槽多 key
keyHashSlot() 对包含 {...} 的
key 只哈希花括号内子串(若 {}
不成对则哈希整 key)。设计意图是让用户把关联 key 钉在同一
slot:
user:{10086}:profile
user:{10086}:orders
于是
MGET、MULTI/EXEC、SUNIONSTORE
等 多 key 命令可在单 slot 内执行。没有 hash
tag 时,(key1, key2)
落槽独立随机,CROSSSLOT
是预期行为而非配置错误。
边界:hash tag 解决的是
键共址,不解决
跨槽事务;resharding 期间同 tag key
仍可能遭遇
ASK/TRYAGAIN,应用需使用
Cluster-aware 客户端并处理重试。
四、cluster.c 路由与命令检查
getNodeByQuery()(cluster.c)根据命令的
key 数量与 slot 计算结果返回 CLUSTER_REDIR_NONE
/ MOVED / ASK /
CROSSSLOT /
TRYAGAIN。clusterRedirectClient()
把错误码编码为客户端可见的 -MOVED /
-ASK 行。
与 Dict
渐进 rehash 对比:单机上 rehash 是
进程内 摊销;Cluster slot 迁移是
跨节点 MIGRATE 原子搬
key,时长与 key 数量、值大小线性相关——大 key
会拖慢槽迁移(链第 15 篇)。
五、槽分配、resharding 与客户端
集群初次创建时,redis-cli --cluster create
会把 16384 slot 分到各 master;扩容用
--cluster add-node 与
--cluster reshard,按 slot
粒度 而非单 key 随机迁移。官方建议迁移期间使用
Cluster-aware
客户端(redis-cli -c、各语言
RedisCluster 驱动),由库解析
MOVED/ASK。
手动诊断命令(生产慎用 CLUSTER RESET):
redis-cli CLUSTER INFO
redis-cli CLUSTER SLOTS
redis-cli CLUSTER KEYSLOT mykey
redis-cli CLUSTER GETKEYSINSLOT 8 10
redis-cli CLUSTER NODESCLUSTER KEYSLOT 验证 hash tag 是否把多 key
钉到同槽:对 user:{42}:a 与
user:{42}:b,二者应返回相同 slot
编号。resharding 期间对同槽 MGET 仍可能遇
TRYAGAIN,应用需退避重试——这与单机
MULTI 永远成功不同。
与 一致性哈希
对照:Jump Hash / Maglev 追求 成员变更时最小 key
搬家;Redis 用 显式 slot 迁移
换取运维可暂停、可限流、可观测的 MIGRATE
进度,二者优化目标不同,不宜用「环更优雅」否定 slot
模型。
六、与复制、Sentinel 的关系
每个 master slot 持有者可有 replica(第 12 篇
复制语义不变)。故障转移由 Cluster 内置
raft-like 选举与配置纪元(configEpoch)协调,与
Sentinel 模式 二选一 部署(第 16
篇决策树)。
复制 lag 与 slot 迁移叠加时:读 replica 可能同时 滞后于主 且 遭遇 ASK;排障需分清是 路由 问题还是 复制 问题。
Sentinel 模式(第 16 篇)在 非 Cluster 部署里提供 master 监视与故障转移,客户端通过 Sentinel 发现当前 master 地址;Cluster 则自带 master 选举,不应 在同一组节点上混用两套逻辑。选型上:单分片 + 自动 failover → Sentinel;写/内存要水平切 → Cluster。官方 Cluster tutorial 与 Sentinel 文档应分开阅读,避免把 Sentinel 的「监控 master」误当成 Cluster 的「slot 所有者」。
七、常见误解
| 误解 | 事实 |
|---|---|
| 「Cluster 自动帮我把 key 均匀分布」 | 均匀性取决于 slot 分配 与 key 哈希;热点 key 仍可打满单 slot |
| 「MOVED 和 ASK 可以一样处理」 | ASK
不能更新永久映射,否则迁移期会把流量错误钉在 IMPORTING
节点 |
| 「16384 个 slot 就要 16384 台机器」 | 一 master 可持多 slot;16384 是 逻辑槽 上限 |
| 「用 proxy 就能随便跨槽事务」 | Proxy 需拆命令或拒绝
CROSSSLOT;原子性范围不会 magically 扩大 |
| 「一致性哈希与 Cluster 可以混用」 | Cluster 客户端应用 slot 表;自行环哈希会导致 找不到数据 |
八、学术谱系、争论与开放问题
谱系:Karger 一致性哈希(1997)→
客户端分片 Memcached → Redis Cluster Spec(Antirez / Redis
Ltd. 文档)固定 16384 slot +
MOVED/ASK 状态机 → 工业界
proxy(twemproxy、Envoy 等)在客户端与 spec 之间折中。
争论:
- 固定 slot vs 动态环:Jump Consistent
Hash、Maglev(见 algorithms/11)在
成员变化时迁移量 上更优,但 Redis Cluster
用 显式 slot 迁移 换取
可运维、可限流 的
resharding(
redis-cli --cluster reshard)。 - 多 key 原子是否值得:强事务需求往往转向 PostgreSQL / TiKV(RocksDB 嵌入路径),Redis Cluster 坚持 同槽 而非两阶段提交,是刻意的复杂度边界。
开放问题:
- 超热点 slot 的 手动拆分(不同业务 key 强制不同 tag)缺乏自动化;社区长期讨论「子 slot」但未改变 16384 模型。
- 跨地域 Cluster 的
MOVED风暴与客户端映射刷新频率,缺少与业务 SLO 绑定的公开对照实验(本篇不编造跨洲延迟数字)。 - 只读副本(
READONLY)在 Cluster 下仍受复制滞后约束;READONLY并不自动解决MOVED,客户端需同时处理 slot 表与 replica 路由(见官方 Cluster specification Readonly replicas 段)。
九、小结
三句话小结:
- Redis Cluster 用 CRC16 mod 16384 把 key
钉到 hash slot,多 key 命令要 同槽
才保原子;
{tag}是工程上共址手段。 MOVED改永久路由,ASK只服务迁移中单次请求;必须配合ASKING,二者不可混为一谈。- 服务端 slot 分片与 Memcached 客户端一致性哈希 是不同范式;扩容时要按 spec 更新 slot 映射,而不是只改环上虚拟节点。
下一篇对照另一套缓存范式:Memcached:多线程 · slab。
参考资料
规范与源码(A 级)
- Redis Documentation, Redis cluster
specification(16384
slots、
MOVED/ASK、ASKING、迁移状态机)。 - Redis OSS 7.4
src/cluster.h:CLUSTER_SLOTS、keyHashSlot、CLUSTER_REDIR_*。 - Redis OSS 7.4
src/cluster.c:getNodeByQuery、clusterRedirectClient。 - Redis Commands,
CLUSTER SETSLOT、ASKING、MIGRATE。
论文(A 级)
- Karger, D. et al., Consistent Hashing and Random Trees, STOC 1997(客户端分片谱系对照)。
辅助(B 级)
返回 系列目录 | 上一篇:复制 | 下一篇:Memcached 对照
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Redis / 缓存内核】TTL 与过期:expires dict · 惰性/主动删除 · lazyfree
拆解 Redis 7.4 的 TTL 机制:expires 辅助表、lookup 路径惰性删除、activeExpireCycle 自适应采样,以及 lazyfree 异步释放大对象对延迟的影响。
【Redis / 缓存内核】maxmemory 策略:近似 LRU/LFU 与 MEMORY DOCTOR
从 evict.c 拆解 volatile/allkeys 与 LRU/LFU/random/ttl 策略矩阵、24 位时钟近似 LRU、淘汰池采样,并对照算法篇真 LRU 与 MEMORY DOCTOR 信号。
【Redis / 缓存内核】RDB:fork COW · BGSAVE · 快照格式
从 rdb.c 拆解 SAVE/BGSAVE 的 fork 写时复制语义、子进程快照路径,以及 REDIS 魔数与 opcode 锚定的 RDB 格式边界。
【Redis / 缓存内核】AOF:命令日志 · rewrite · appendfsync
从 aof.c 拆解 AOF 多段清单、feedAppendOnlyFile 写路径、后台 rewrite 与 appendfsync always/everysec/no 各自的丢失窗口及 fsync 失败语义。