前言
在上一篇文章中,我们拆解了主从复制和哨兵机制——它们解决了单机Redis的高可用问题。但哨兵方案有一个根本性局限:所有节点存的是同一份全量数据。如果数据量超过单机内存上限,哨兵也无能为力。
这就是Redis Cluster要解决的问题——把数据分散到多台机器上,每台只存一部分数据,突破单机内存瓶颈。面试中,Cluster是Redis进阶的必考题:
“Redis Cluster的数据分片原理是什么?”
“为什么是16384个哈希槽?”
“MOVED重定向和ASK重定向有什么区别?”
“Cluster有什么局限性?”
本文从哈希槽的原理出发,拆解Cluster的请求路由、故障转移和局限性。
本文核心问题:
- Redis Cluster的数据分片原理是什么?为什么是16384个哈希槽?
- MOVED重定向和ASK重定向有什么区别?
- Cluster模式下的故障转移是怎么做的?
- Cluster有什么局限性?什么场景不适合用Cluster?
- 一致性哈希和哈希槽有什么区别?
- 秒杀项目为什么选哨兵而不是Cluster?
读完本文,你将对Redis Cluster拥有从原理到选型的完整理解。
一、为什么需要Cluster?
疑问:有了哨兵,为什么还需要Cluster?
回答:哨兵解决的是"高可用"问题——主库宕机能自动切换。Cluster解决的是"数据量大"问题——单机内存存不下全部数据时,分散到多台机器上。
哨兵方案的局限: 主库存储全量数据 → 数据量必须 < 单机内存上限 从库复制全量数据 → 从库也需要同样的内存 当数据量超过单机内存: 哨兵无能为力 → 只能升级机器内存(垂直扩展) 但内存有上限 → 256GB之后成本暴涨 → 必须以数量换容量(水平扩展)Cluster的本质:把数据分片存储在多台Redis实例上,每个实例只存一部分数据。多台机器的内存加起来,总容量远超单机上限。
二、哈希槽——数据分片的核心
疑问:数据怎么决定存到哪个节点?为什么是16384个槽?
回答:Redis Cluster将整个键空间划分为16384个哈希槽。每个Key通过CRC16(key) % 16384计算所属槽位,每个主节点负责一部分槽位。
2.1 槽位分配
┌─────────────────────────────────────────────────┐ │ Redis Cluster │ │ │ │ 槽 0-5460 槽 5461-10922 槽 10923-16383 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 主节点A │ │ 主节点B │ │ 主节点C │ │ │ │ (写) │ │ (写) │ │ (写) │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ │ │ 从节点 │ │ 从节点 │ │ 从节点 │ │ │ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────┘2.2 为什么是16384?
这个数字来自两个约束的折中:
| 考量 | 说明 |
|---|---|
| 槽数太少 | 数据分布不均匀,部分节点负载过重。如果只有16个槽分给3个节点,必然有节点比其他节点多承担几个槽 |
| 槽数太多 | 心跳消息中需要携带本节点的槽位信息,槽数越多心跳包越大。Cluster内部通过Gossip协议定期交换信息,槽位信息占心跳包的主体 |
| 16384 | 足够均匀(每个节点可分配数千槽,迁移时可以单个槽搬迁),心跳包大小控制在合理范围内 |
2.3 一致性哈希 vs 哈希槽
| 维度 | 一致性哈希 | Redis哈希槽 |
|---|---|---|
| 分片方式 | 哈希环,每个节点负责一段弧 | 固定16384个槽,分配给各节点 |
| 扩缩容 | 只有相邻节点受影响 | 槽可以在任意节点间迁移 |
| 实现复杂度 | 客户端实现环的查找逻辑 | 服务端管理槽分配,客户端只按槽查询 |
| 数据倾斜 | 可能(虚拟节点缓解) | 槽数固定,均匀分配,人工控制平衡 |
哈希槽的核心优势:槽是数据迁移的最小单位。扩容时,从每个现有节点迁几个槽给新节点,数据迁移量和迁移源均匀分散——所有现有节点各移一部分槽,新节点迅速获得均衡的负载。一致性哈希的增删节点只影响环上的相邻节点,其余节点数据不动——操作量少但不均衡。
三、请求路由——客户端怎么找到数据?
疑问:客户端随便连一个节点就能查到数据吗?
回答:可以连任意节点,但数据可能不在这个节点上。Redis Cluster用MOVED和ASK两种重定向告诉客户端"数据在哪"。
3.1 MOVED重定向——槽已经迁移走了
客户端 → 节点A:GET key1 节点A:计算CRC16(key1)%16384 = 槽5000 节点A:槽5000在节点B → 返回 MOVED 5000 节点B的地址 客户端 → 重新连接节点B → GET key1 → 成功MOVED表示"这个槽已经永久不属于我了,去找它的新主人"。客户端收到MOVED后,应该更新本地的槽位映射表,下次直接访问正确节点。
3.2 ASK重定向——槽正在迁移中
槽5000正在从节点A迁移到节点B: 部分Key还在节点A,部分Key已迁到节点B 客户端 → 节点A:GET key1 节点A:key1还在我这 → 直接返回 ✓ 客户端 → 节点A:GET key2 节点A:key2已经迁到节点B → 返回 ASK 5000 节点B的地址 客户端 → 节点B:ASKING + GET key2 → 成功ASK是临时性的——“这个Key在迁移过程中被搬走了,你临时去新节点找它”。客户端收到ASK后,不能更新本地槽位映射。槽迁移完成后,ASK不再出现,统一为MOVED。
3.3 MOVED vs ASK
| 维度 | MOVED | ASK |
|---|---|---|
| 含义 | 槽永久归新节点 | Key临时在新节点 |
| 时机 | 槽迁移完成后 | 槽迁移进行中 |
| 客户端行为 | 更新本地槽位表 | 不更新槽位表,仅本次去新节点查询 |
| 效率 | 第二次就直接访问正确节点 | 每次都要先试旧节点,再被重定向到新节点 |
四、Cluster的故障转移
疑问:Cluster模式下主节点挂了怎么办?
回答:Cluster内部通过Gossip协议自动检测故障并选举新主,不需要外部哨兵。
4.1 故障检测
1. 节点间定时发送PING/PONG消息(Gossip协议) 2. 如果节点B在超时时间内没有回复节点A的PING 3. 节点A将节点B标记为PFAIL(Possible Failure,疑似下线) 4. 节点A通过Gossip传播这个疑似下线的信息 5. 当超过半数的主节点都认为节点B疑似下线 6. 节点B被标记为FAIL(确认下线)4.2 故障转移
1. 节点B被标记为FAIL 2. 节点B的从节点们发起选举 3. 从节点向所有主节点请求投票 4. 获得半数以上主节点投票的从节点当选为新主节点 5. 新主节点接管节点B的槽位 6. 通过Gossip广播新主节点信息故障转移条件和哨兵的客观下线类似——需要多数主节点确认,防止网络分区导致的误判。两个主节点互相认为对方挂了的网络分区场景,只有和大多数主节点在同一分区的从节点才能获得足够票数当选,防止split-brain。
五、Cluster的局限性
疑问:Cluster这么好,是不是所有场景都该用它?
回答:不是。Cluster有几个硬性限制,在不适用它的场景下强行使用会引入不必要的复杂度。
5.1 批量操作的Key限制
# ❌ Cluster下可能失败MSET key1 value1 key2 value2# key1和key2可能在不同节点# ✅ 解决方案:用Hash Tag强制多个Key落在同一个槽MSET{user:1001}:name"张三"{user:1001}:age25# 只对{}内的部分计算哈希 → user:1001 → 相同的槽5.2 跨槽事务
Cluster不支持跨槽事务——事务中的所有Key必须在同一个槽中,否则返回错误。多个Key的操作要么事先规划确保同槽,要么使用Hash Tag显式约束。
5.3 多键操作
SINTER、SUNION、SDIFF等操作多个Key的命令,如果Key不在同一节点直接报错。需要对数据分布有充分的提前规划。
5.4 数据库选择
Cluster只支持db0——SELECT命令不支持切换数据库。这和Cluster的请求路由设计相关——不同节点的槽位信息是全局统一的,数据库切换会引入额外的状态管理。
5.5 适用场景判断
| 条件 | 哨兵 | Cluster |
|---|---|---|
| 数据量 < 单机内存 | ✅ | 🟡 可但不需要 |
| 数据量 > 单机内存 | ❌ | ✅ |
| 需要跨Key事务 | ✅(同一主库内所有Key都可用) | ❌(有限制) |
| 需要多键聚合操作 | ✅ | ❌(有限制) |
| 运维复杂度 | 低 | 中 |
六、秒杀项目为什么选哨兵而不是Cluster?
秒杀系统的Redis数据量在2GB以内——单机内存完全容纳,没有分片的必要。秒杀中库存扣减需要原子操作,Lua脚本的多Key操作在哨兵单机内可以自由完成,Cluster下必须用Hash Tag显式约束Key分布。Cluster的部署和管理复杂度高于哨兵,在没有明确收益时引入新风险得不偿失。
如果未来数据量增长到需要Cluster,迁移路径是清晰的:所有库存和缓存的Key都用Hash Tag约束到同一槽内,客户端升级为JedisCluster或Lettuce的Cluster模式,逐节点迁槽完成平滑过渡。
七、面试中这样回答
面试官:“Redis Cluster的数据分片原理是什么?”
回答框架:
“Redis Cluster用哈希槽做数据分片。整个键空间划分为16384个槽,每个Key通过CRC16计算所属槽位,每个主节点负责管理一部分槽。客户端请求路由到任意节点,如果数据不在该节点,会收到MOVED重定向。16384这个数是数据均匀分布和心跳包大小之间的折中——槽数太少会导致节点间负载不均,槽数太多会导致心跳包过大。”
面试官:“MOVED和ASK有什么区别?”
回答:
“MOVED是永久重定向——槽已经迁移完成,客户端应该更新本地槽位表,下次直接访问新节点。ASK是临时重定向——槽正在迁移过程中,单个Key临时位于新节点,迁移完成后ASK消失。ASK不影响客户端槽位表缓存,因为槽的归属还没正式变更。”
总结
- Cluster用哈希槽做数据分片:总16384个槽,每个Key通过
CRC16 % 16384定位槽,槽均匀分配给各主节点 - MOVED和ASK是两种重定向:MOVED永久(槽迁移完成→更新客户端本地槽表),ASK临时(迁移过程中个别Key→下次继续试原节点)
- Gossip协议实现去中心化的故障检测和广播:节点间PING/PONG交换状态,超过半数主节点确认后触发故障转移
- Cluster的局限:跨槽事务和多键操作需Hash Tag强制约束,只支持db0,批量操作Key分布在不同节点时可能失败
- 哨兵 vs Cluster的选择不是"哪个更好",而是"当前需要什么"——数据量大于单机内存才需要Cluster的分片能力
- 项目选哨兵:2GB内数据单机容纳,库存扣减Lua多Key操作不需要跨节点协调,只在需要时平滑迁移到Cluster
下一篇预告:Redis原理(七)——Redis分布式锁:从setnx到RedLock的演进之路。拆解传统分布式锁的缺陷、Redisson WatchDog的自动续期机制、Redis集群下锁的安全隐患,以及RedLock算法的工作原理和学术争议。