引言
Redis 集群主要有两种形态:
- 主从哨兵,用来保障高可用
- 切片集群,用来实现水平扩展
前者通过哨兵监控与自动切换解决单点故障问题,后者基于哈希槽机制把数据分散到多个实例上,并通过重定向机制应对节点和槽位关系变化。
本文主要梳理这两种方案的核心设计,以及它们在定位目标上的差异。
Redis 两种集群方式
Redis 常见的集群方式主要有两种:
- 主从集群
- 切片集群
主从集群
主从集群中:
- 主节点负责读写操作
- 从节点主要负责复制主节点数据,提供数据备份能力
在常规主从模式下,从节点通常不负责自动选主,因此如果主节点宕机,集群本身并不能自动完成故障恢复。
哨兵模式
哨兵模式可以理解为主从集群的一种升级。
它的作用主要是:
- 监控主节点和从节点状态
- 在主节点宕机后帮助完成自动选主
因此,哨兵模式重点解决的是:
- 高可用问题
但哨兵模式本身并不解决数据水平扩展问题,也不支持像切片集群那样基于数据分布做在线扩容。
切片集群
切片集群采用哈希槽来处理数据和实例之间的映射关系。
一个 Redis Cluster 集群一共有:
- 16384 个哈希槽
这些哈希槽可以理解成一组固定编号的数据分区。每个键值对都会根据它的 key,被映射到其中一个哈希槽上,然后由负责该槽的实例来存储和处理这个 key。
所以切片集群重点解决的是:
- 数据水平扩展问题
切片集群中的槽分布
在部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群。
创建完成后,Redis 会把 16384 个槽尽量平均分布在多个主节点实例上。
例如:
- 如果集群中有
N个主节点实例 - 那么每个实例大致会负责
16384 / N个槽
这样一来,数据就被切分到了不同实例中,实现了横向扩展。
切片集群中的从节点作用
在 Redis Cluster 模式下,从节点依然存在,但它的角色是:
- 作为主节点的备用副本
- 复制主节点数据
- 在主节点故障时参与故障转移
默认情况下,从节点本身不承担正常读写服务,也不是主要的数据访问入口。
所以切片集群里:
- 主节点负责槽和数据
- 从节点负责冗余和故障恢复
哈希槽映射过程是怎样的
Redis Cluster 中,一个 key 会映射到哪个实例,本质上要先经过"key -> 槽"的映射。
这个过程通常分为两步:
- 先根据 key 计算哈希值
- 再根据哈希值映射到某个槽
更具体地说:
- Redis 会对 key 按照 CRC16 算法计算出一个 16 bit 的值
- 再用这个值对 16384 取模
- 最终得到一个
0 ~ 16383范围内的数字
这个数字就代表该 key 所属的哈希槽编号。
所以:
- key 先映射到槽
- 槽再映射到实例
这就是 Redis Cluster 的基本数据定位方式。
切片集群客户端如何定位数据
客户端和 Redis Cluster 实例建立连接后,需要知道:
- 哪些哈希槽归哪个实例负责
Redis Cluster 会在实例之间传播哈希槽分配信息,因此各个实例都会保存整个集群的槽位映射关系。
当客户端连接某个实例后,这个实例也会把对应的槽分布信息返回给客户端。这样客户端本地就会缓存:
- 槽 -> 实例
的映射关系。
之后,客户端在发送请求时,就可以根据 key 所属槽,直接把命令发往正确实例。
实例和哈希槽关系变更后,客户端如何定位数据
Redis Cluster 提供了重定向机制来解决这个问题。
所谓"重定向",就是:
- 客户端把请求发给了一个实例
- 但这个实例并不负责该 key 所在的槽
- 于是实例告诉客户端应该去找哪一个新实例
客户端再根据返回结果,重新发起请求。
这种机制主要出现在:
- 集群扩容
- 集群缩容
- 哈希槽迁移
- 槽重新均衡
等场景下。
常见的变更原因
实例与哈希槽对应关系发生变化,通常有两个常见原因:
- 集群新增或删除实例,需要重新分配槽
- 为了做负载均衡,需要在多个实例之间重新分布哈希槽
也就是说,只要槽位归属发生变化,客户端原来缓存的槽位映射信息就可能失效。
重定向机制
Redis Cluster 常见的重定向主要有两种:
MOVEDASK
它们虽然都表示"你要去别处访问",但语义并不一样。
全部迁移:MOVED
如果某个槽的数据已经全部迁移完成,那么客户端再访问旧实例时,旧实例会返回:
MOVED
例如:
text
GET hello:key
(error) MOVED 13320 172.16.19.5:6379
这表示:
- 槽
13320已经正式迁移到172.16.19.5:6379
客户端收到 MOVED 后,需要做两件事:
- 把这次请求重新发到新的实例
- 更新本地缓存中的"槽 -> 实例"映射关系
所以 MOVED 的含义是:
- 槽的归属已经正式改变了
- 后续请求都应该发往新实例
部分迁移:ASK
如果某个槽还处于迁移过程中,那么客户端访问旧实例时,可能会收到:
ASK
例如:
text
GET hello:key
(error) ASK 13320 172.16.19.5:6379
这通常意味着:
- 这个槽并没有完全迁移完成
- 只是当前访问的这个 key,已经被迁移到了新实例
此时客户端需要:
- 先向新实例发送
ASKING - 再把真正的操作命令发给新实例
但要注意:
- 客户端不会更新本地缓存中的槽位映射关系
因为 ASK 只表示一次临时重定向,而不是槽归属已经永久变化。
ASK 和 MOVED 的区别
这两个命令最核心的区别在于:
MOVED
- 表示槽已经完成迁移
- 客户端需要更新本地槽位缓存
- 后续请求都要发往新实例
ASK
- 表示槽还在迁移过程中
- 客户端只需要临时把这一次请求发往新实例
- 不更新本地槽位缓存
所以可以简单记忆为:
MOVED是永久重定向ASK是临时重定向
主从哨兵和切片集群怎么选
可以从目标上直接区分:
主从哨兵
更适合解决:
- 单点故障
- 自动故障切换
- 高可用问题
切片集群
更适合解决:
- 单机内存不足
- 单机吞吐不够
- 需要水平扩展
因此两者并不是简单替代关系,而是:
- 哨兵偏高可用
- 切片偏可扩展
总结
这篇文章可以压缩成几条核心结论:
- Redis 集群常见有两种方式:主从哨兵和切片集群
- 主从哨兵主要解决高可用问题,切片集群主要解决水平扩展问题
- Redis Cluster 使用 16384 个哈希槽实现 key 到实例的映射
- key 先通过 CRC16 计算,再对 16384 取模,确定所属槽
- 客户端通过缓存槽位映射关系来定位数据
MOVED表示槽已经正式迁移完成,客户端需要更新缓存ASK表示槽仍在迁移中,只做一次临时重定向
理解 Redis 集群,关键不是只记住"有哨兵"和"有哈希槽",而是要看清它们分别解决的是哪一类问题。
如果这篇文章对你有帮助,欢迎继续阅读本系列后续内容。若文中有不准确或需要补充的地方,也欢迎指出。