Redis Cluster 集群深度解析:哈希槽、Gossip协议与故障转移
兄弟们,你们有没有遇到过这种情况:单机Redis扛不住QPS,主从+哨兵解决了高可用,但写操作还是只能打在一个节点上,扩容还得停机?上周五晚上9点,我正吃着火锅唱着歌,突然收到报警——业务QPS冲到15万,单机Redis CPU飙到95%,主从切换都救不了,因为根本问题是:写不进去了。
这时候你才意识到,单机再强也有天花板,主从+哨兵再稳也扛不住写流量的爆炸式增长。今天咱不聊虚的,直接从源码和底层设计出发,把Redis Cluster的三个核心——哈希槽、Gossip协议、故障转移——彻底讲透。
一、为什么非得用Cluster?主从+哨兵不香吗?
先说说主从+哨兵这套组合的痛点。
第一,写能力是单点瓶颈。 主从模式下,所有写操作只能由Master承担。Master的CPU、内存、网络带宽再大,也扛不住无限增长的写入流量。某电商平台的真实案例:单机Redis QPS到12万就卡死了,而业务预期是30万+。
第二,数据容量受单机内存限制。 现在动辄几百GB的缓存数据,单机内存根本装不下。就算你土豪到上TB内存,成本也扛不住。
第三,扩容需要停机或复杂操作。 主从+哨兵模式下,想要扩展容量,要么换更大内存的机器(垂直扩容),要么自己搞分片逻辑(水平扩容,但应用层得大改)。
Redis Cluster就是为解决这三个问题而生的。官方对Cluster的设计目标有三条:
高性能和线性扩展:最多支持1000个节点,无代理层
可接受的写安全:尽最大努力保留已确认的写入
高可用:多数主节点可达时集群即可存活
说白了,Cluster要同时解决容量(分片存储)、性能(多点写入)和可用性(自动故障转移)三大问题。
二、核心一:哈希槽——数据分片的核心算法
2.1 什么是哈希槽?
Redis Cluster把整个键空间划分成 16384 个哈希槽(hash slot),编号从0到16383。每个key通过下面的公式计算归属槽位:
slot = CRC16(key) & 16383
每个Master节点负责一部分槽位。比如3节点集群:
| 节点 | 负责槽位范围 |
|---|---|
| Master A | 0 – 5460 |
| Master B | 5461 – 10922 |
| Master C | 10923 – 16383 |

数据与节点的解耦是这种设计最大的价值。数据只绑定槽位,不直接绑定节点。新增节点时,只需要从现有节点“借”一部分槽位过来就行,不用重新计算所有key的位置。
2.2 为什么偏偏是16384?
这个问题我当年也好奇过。CRC16算法产生的hash值有16位,理论上可以产生65536个值,那为啥不取65536呢?
Redis作者antirez在GitHub issue #2576里亲自回答过这个问题:
原因一:心跳包大小
节点间定期发送心跳包,里面要携带完整的槽位配置信息。这个配置用bitmap表示——每个bit代表一个槽,1表示这个槽属于该节点。16384个槽只需要 2KB(16384/8/1024)。如果改成65536个槽,就需要 8KB。节点之间每秒都在发PING/PONG,8KB的心跳包在网络中传播,开销会大很多。
原因二:集群规模限制
Redis Cluster的设计上限是 1000个主节点。16384个槽分给1000个节点,每个节点平均分到约16个槽,足够用了。再多槽位没有实际意义,只会增加网络开销。
原因三:bitmap压缩困难
小集群里,bitmap压缩效果很差。因为节点少,每个节点负责的槽占比大,bitmap里大量bit被置1,压缩算法基本失效。
说白了,16384是在“集群规模上限”和“网络传输开销”之间找到的最优平衡点。
2.3 哈希标签:让多个Key待在同一个槽
Redis Cluster默认不支持跨槽的多key操作(比如MSET、MGET、事务)。如果多个key落在不同槽,会报CROSSSLOT错误。
哈希标签就是用来解决这个问题的:只对{}内的内容计算CRC16。
# 普通:两个key可能在不同槽 SET user:1000:name "Alice" SET user:1000:age "25" # 使用哈希标签:强制同槽 MSET {user:1000}:name "Alice" {user:1000}:age "25"
适用场景:需要原子操作的多key场景,比如购物车批量更新、用户信息聚合查询。
2.4 槽位迁移:MOVED与ASK
集群扩缩容时,槽位需要在节点间迁移。迁移过程中,客户端可能遇到两种重定向:
| 响应 | 含义 | 客户端该怎么做 |
|---|---|---|
| MOVED | 槽已永久迁移到新节点 | 更新本地缓存,后续直接访问新节点 |
| ASK | 槽正在迁移中,本次临时重定向 | 先发ASKING命令,再重试请求 |

MOVED是永久性的,客户端应该更新本地槽映射表;ASK是临时性的,只对当前请求生效,后续请求仍可能回到源节点。
三、核心二:Gossip协议——去中心化的通信机制
3.1 为什么不用中心化?
有些分布式系统用Zookeeper、etcd这类中心化组件来管理集群元数据。好处是强一致,坏处是引入了额外的组件和单点压力。
Redis Cluster选择了一条不同的路:完全去中心化。所有节点通过Gossip协议互相通信,每个节点都保存完整的集群拓扑信息。
每个Redis节点需要开放 两个TCP端口:
-
数据端口(默认6379):给客户端用
-
集群总线端口(默认数据端口+10000,即16379):节点之间用二进制协议通信
3.2 四种消息类型
| 消息 | 作用 |
|---|---|
| PING | 探测节点存活,携带自身状态和部分集群信息 |
| PONG | 对PING/MEET的响应,也用于信息广播 |
| MEET | 新节点加入时,向集群“自我介绍” |
| FAIL | 宣布某节点已下线,广播给所有节点 |
3.3 传播机制
每个节点每秒会做两件事:
-
随机选5个节点发送PING
-
遍历所有节点,挑出
pong_recv > timeout/2的节点发PING(太久没通信的优先)
收到PING的节点回复PONG,携带自己的状态信息。这种“随机+优先级”的混合策略,既保证了信息传播的效率,又避免了全量广播带来的网络风暴。

Gossip的特点:
-
随机传播:不依赖特定节点,信息像病毒一样扩散
-
低负载:通信频率不随集群规模线性增长
-
最终一致性:信息有短暂延迟,但最终所有节点达成一致
3.4 故障检测:PFAIL → FAIL 两阶段判定
第一步:主观下线(PFAIL)
节点A向节点B发PING,在cluster-node-timeout(默认15秒)内没收到PONG,A就把B标记为PFAIL。注意,这只是A“觉得”B可能挂了。
第二步:客观下线(FAIL)
PFAIL只是单个节点的“怀疑”。当半数以上持有槽的Master都认为某节点PFAIL时,该节点被正式标记为FAIL,并通过Gossip广播FAIL消息。

为什么搞两阶段? 防止网络抖动导致误判。你自己的网络可能出问题,不代表节点真的挂了。
四、核心三:故障转移——自动Failover
4.1 从节点选举
Master被标记为FAIL后,它的Slave们会发起选举,竞争成为新Master。

投票规则:
-
每个Master在每个epoch(配置纪元)只能投一票
-
只有持有槽的Master有投票权(Slave不参与投票)
-
获得半数以上(
N/2 + 1)票的Slave当选
4.2 选举时机与优先级
Slave不会同时发起选举,而是根据数据新旧程度错开时间:
-
每个Slave计算自己的
SLAVE_RANK:数据越新,rank越小 -
延迟 =
FAILOVER_DELAY+rank × 延迟增量 -
rank越小(数据越新),等待越短
-
保证数据最新的Slave优先发起选举
这个设计很巧妙——数据最新的节点最应该成为新Master,给它最短的等待时间,大概率第一个发起选举并获胜。
4.3 晋升与广播
当选的Slave执行 SLAVEOF NO ONE 成为Master,然后广播PONG消息(携带新的epoch和槽位信息),其他节点更新集群状态表。

整个过程无需人工干预,官方文档说通常在1-2秒内完成
五、脑裂问题与数据丢失
5.1 脑裂是怎么发生的?
网络分区时可能出现这样的情况:旧Master被隔离在一个孤立的网络分区里,它不知道集群已经选出了新Master,还在继续接收客户端的写请求。
等网络恢复后,旧Master发现自己“过时”了,只能降级为Slave。但它在孤立期间收到的那些写入,就永远丢失了。
5.2 怎么防?
Redis提供了两个配置参数来缓解脑裂问题:
# 至少要有N个从节点同步成功,才能继续写入 min-replicas-to-write 1 min-replicas-max-lag 10
这两个参数的意思是:如果当前Master无法将数据同步到至少1个从节点(且延迟超过10秒),就拒绝写入。在网络分区时,孤立的老Master因为无法同步数据,会拒绝写入,从而减少数据丢失。
坦白说:这个方案不能100%防止脑裂,但能把数据丢失的窗口从“分钟级”缩小到“几乎不发生”。生产环境强烈建议开启。
六、生产环境最佳实践
6.1 节点规划
| 建议 | 说明 |
|---|---|
| 最小6节点 | 3主3从,确保选举能获得多数投票 |
| 奇数个主节点 | 3/5/7个,保证故障容忍度 |
| 主从跨机架 | 主从部署在不同物理机/可用区,避免单点故障 |
| 每个主节点配1-2个从节点 | 保证高可用 |
6.2 关键配置参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
cluster-node-timeout |
15000ms | 故障检测超时。过小容易误判,过大故障恢复慢 |
cluster-require-full-coverage |
no | 允许部分分片不可用时继续服务 |
min-replicas-to-write |
1 | 防止脑裂导致的数据丢失 |
cluster-migration-barrier |
1 | 从节点迁移屏障 |
6.3 客户端使用
-
使用支持集群的客户端(JedisCluster、Lettuce),自动处理MOVED/ASK重定向
-
客户端启动时通过
CLUSTER NODES获取完整拓扑,缓存槽位映射 -
对于
MGET/MSET等多key操作,确保所有key在同一槽位
某电商平台的实践数据显示:从主从+哨兵迁移到原生Redis Cluster后,QPS从12万提升到38万,延迟降低62%。
七、总结:三个核心环环相扣
Redis Cluster的三个核心设计是相互支撑的:

-
哈希槽:把数据空间切成16384份,解耦数据与节点。扩容缩容只需要移动槽位,不用重新哈希所有数据
-
Gossip协议:无中心通信。PING/PONG闲聊间完成状态同步和故障检测,避免了中心节点的单点瓶颈
-
故障转移:PFAIL→FAIL两阶段判定 + Raft风格选举,1-2秒自动恢复
说白了,Redis Cluster就是用哈希槽解决了“数据放哪”的问题,用Gossip解决了“大家怎么知道数据放哪”的问题,用故障转移解决了“节点挂了怎么办”的问题。三者缺一不可。
兄弟们,你们在生产环境中遇到过Redis Cluster的坑吗?槽迁移时业务抖动、节点加入失败、脑裂丢数据……评论区聊聊,一起分析分析。
浙公网安备 33010602011771号