
1. 从一次凌晨告警说起Redis Cluster 故障转移到底在做什么凌晨两点十七分手机开始疯狂震动。监控面板上某个业务集群的写入延迟从 2ms 飙到 800ms紧接着一连串CLUSTERDOWN报错刷屏。我爬起来连上跳板机cluster info一看——三个主节点里有一个已经标记为fail但从节点迟迟没有接管整个哈希槽区间处于不可用状态。那次事故让我彻底意识到Redis Cluster 的故障转移不是配好了就自动生效的魔法它背后有一整套投票、选举、纪元推进的机制任何一个环节配置不当都会让高可用变成纸面文章。这篇文章我想把 Redis Cluster 故障转移这件事从头到尾讲透。它是什么简单说就是当集群中某个主节点宕机后它的从节点通过一套分布式协商机制自动升级为主节点接管原来负责的哈希槽让整个集群继续对外提供服务。能解决什么问题解决单点故障导致的写入中断、数据不可用。适合谁看正在用或准备用 Redis Cluster 做缓存/存储的运维、后端开发以及被集群为什么没自动切换折磨过的同行。我不会只贴几行配置了事。下面会拆解故障转移的完整链路主观下线怎么判定、客观下线怎么达成、从节点怎么拉票、主节点怎么投票、新主怎么接管槽位、旧主回来怎么办。每一环我都会说清楚为什么这么设计以及实际踩过什么坑。看完你应该能自己排查为什么我的集群没切换这类问题而不是干等着它自己好。2. 故障转移的底层逻辑先搞懂 Cluster 的权力结构2.1 哈希槽、节点角色与集群总线Redis Cluster 把整个键空间划分成 16384 个哈希槽slot每个主节点负责其中一部分槽。键通过CRC16(key) mod 16384定位到具体槽再找到负责该槽的节点。从节点默认只做数据复制不参与读写除非开启READONLY做只读从库读。节点之间靠Cluster Bus通信这是一个独立的二进制协议端口默认是客户端端口 10000。比如客户端端口 7000总线端口就是 17000。这个细节非常关键——很多集群状态异常但节点都活着的问题根源就是总线端口被防火墙拦了。总线负责 gossip 消息传播、故障检测、配置纪元config epoch同步、投票选举。可以说故障转移的全部协商都跑在这条总线上。提示部署时务必确认port 10000的连通性云环境安全组、容器网络、K8s NetworkPolicy 都要放行。我见过太多案例是客户端端口通、总线端口不通结果集群永远处于fail状态却不自知。2.2 为什么是去中心化投票而不是选主哨兵很多人第一反应是Redis 不是有 Sentinel 吗为什么 Cluster 不用 Sentinel 那套原因是架构目标不同。Sentinel 是独立进程监控的是普通主从而 Cluster 本身就是多主多从的分布式结构节点数量动态变化如果再加一层外部哨兵部署复杂度和一致性风险都会上升。Cluster 选择让主节点自己充当投票人从节点充当候选人通过 gossip 协议在集群内部完成选举。这套机制的好处是没有外部依赖坏处是——它依赖多数主节点存活这个前提。一旦超过半数的主节点挂掉集群就无法完成任何选举直接进入不可用。这就是为什么 Cluster 的规模规划里主节点数量最好是奇数且要保证故障域分散。2.3 故障转移的完整链路概览在深入细节前先用一张表把整个流程的六个阶段串起来后面每一节都会展开阶段触发条件关键动作涉及参数主观下线 PFAIL单节点心跳超时标记对方为疑似故障cluster-node-timeout客观下线 FAIL多数主节点确认广播 FAIL 消息需多数主节点达成一致从节点发起选举主节点 FAIL 且从节点数据足够新递增 currentEpoch 拉票cluster-replica-validity-factor主节点投票收到拉票请求每个纪元只投一票需获得多数主节点票新主接管槽位得票过半提升自己为主接管槽configEpoch 更新旧主回归原主恢复降级为从节点自动跟随新主这张表建议收藏排查问题时按阶段对号入座能快速定位卡在哪一步。3. 主观下线与客观下线故障判定不是一断就切3.1 心跳机制与 cluster-node-timeout 的真实含义每个节点会定期向其他节点发送PING收到PONG就算活着。如果超过cluster-node-timeout默认 15000ms没收到有效响应就标记对方为PFAIL主观下线。注意主观两个字——这只是单个节点的判断不代表集群共识。这里有个常见误解很多人以为把cluster-node-timeout调小就能更快切换。理论上是的但代价是误判率飙升。网络抖动、GC 停顿、慢查询阻塞都可能导致短暂无响应。timeout 设成 1 秒一次 Full GC 就可能触发一轮不必要的选举反而制造抖动。我的经验值内网环境 5~10 秒跨机房或云环境 15~30 秒。判断依据是你的正常网络 P99 延迟 最大 GC 停顿要明显小于 timeout。如果业务对切换时间极度敏感与其压 timeout不如优化从节点数量和网络质量。3.2 从 PFAIL 到 FAIL多数派确认的数学单个节点标记 PFAIL 后会通过 gossip 把我认为 X 挂了这个消息传播出去。当负责处理槽位的主节点中超过半数都认为 X 处于 PFAIL 或 FAIL 状态时某个节点就会把 X 标记为FAIL客观下线并广播一条 FAIL 消息强制所有节点接受。这里的多数是主节点总数的多数不是所有节点的多数。假设有 3 个主节点需要 2 个确认5 个主节点需要 3 个如果有 6 个主节点需要 4 个超过半数。这就引出一个致命约束注意如果挂掉的主节点数量达到或超过半数剩余主节点无法凑齐多数票故障转移永远不会触发。比如 3 主 3 从的集群挂掉 2 个主集群直接不可用从节点再多也没用。所以集群规划时主节点数量决定了容错上限。3 主最多容忍 1 个主挂5 主最多容忍 2 个。想要更高容错就得加主节点而不是加从节点。3.3 一个容易忽略的细节FAIL 消息的传播与纪元FAIL 消息带有configEpoch信息。节点收到 FAIL 消息后如果消息里的 epoch 比自己记录的新就更新本地状态。这套机制保证了故障判定在集群内的一致性——不会出现A 认为 X 挂了B 还认为 X 活着的长期分裂。但这里有个坑如果网络分区导致集群被切成两半少数派那一侧的主节点会被多数派标记为 FAIL然后它们的从节点会尝试发起选举。如果少数派侧的主节点还活着它会拒绝投票因为它认为自己的主还在选举就会失败。这正是 Cluster 保证一致性的方式——宁可少数派不可用也不产生脑裂双主。4. 从节点选举一场需要多数票的竞选4.1 选举资格数据够新才有资格参选主节点被标记 FAIL 后它的从节点不会立刻全部冲上去抢。每个从节点会先做一次资格自检核心是判断自己的数据是否足够新。判断依据是(now - master_last_io_seconds_ago) cluster-node-timeout * cluster-replica-validity-factor如果从节点与主节点最后一次数据交互距今太久超过 timeout 乘以 validity-factor就认为数据太旧放弃参选。cluster-replica-validity-factor默认是 10意味着从节点最多容忍 10 倍 timeout 的数据延迟。这个参数的设计意图是防止数据严重滞后的从节点被选为新主造成数据大量丢失。但实际运维中默认值 10 往往太宽松。我一般会把它调到2~5配合合理的 timeout让数据最新的从节点优先当选。如果设成 0则表示不做数据新鲜度检查任何从节点都能参选——除非你完全不在乎数据一致性否则不建议。4.2 拉票过程currentEpoch 递增与投票规则有资格的从节点会发起选举流程是这样的从节点把自己的currentEpoch加一进入新的选举纪元。向所有主节点广播FAILOVER_AUTH_REQUEST请求投票。主节点收到请求后检查这个纪元我投过票没有请求的 epoch 是否比我记录的新发起者是不是我负责的槽对应的从节点满足条件就回FAILOVER_AUTH_ACK并记录本纪元已投票。从节点统计票数拿到多数主节点的票就当选。关键规则每个主节点在每个 epoch 只能投一票。这防止了同一纪元内多个从节点同时当选。如果两个从节点同时拉票先到的拿到票后到的只能等下一个 epoch 重试。4.3 为什么会有选举延迟和重试从节点发起选举不是立即的而是有一个延迟窗口计算方式是DELAY 500ms random(0 ~ 500ms) SLAVE_RANK * 1000ms其中SLAVE_RANK是数据新鲜度排名数据最新的 rank 为 0。这个设计非常巧妙数据最新的从节点延迟最短最先发起选举最可能当选。随机部分则避免了多个同 rank 从节点同时拉票导致的分票。如果第一轮选举失败比如票数不够从节点会等待一个随机时间后重试直到成功或主节点恢复。这就是为什么有时候你会看到主挂了十几秒才切换——可能前几轮选举因为各种原因失败了。实操心得如果发现切换特别慢先看cluster-node-timeout再看从节点数量。从节点太多比如一个主挂 5 个从会加剧分票和重试反而拖慢切换。一般一个主配 1~2 个从就够追求极致可用可以配 2 个。5. 新主接管与旧主回归槽位迁移的收尾工作5.1 接管槽位configEpoch 的更新与广播从节点当选后会做几件事把自己提升为主节点接管原主负责的所有哈希槽更新自己的configEpoch为当前最大 epoch 1然后通过 gossip 广播我现在是这些槽的主人了。其他节点收到广播后更新本地的槽位映射表。客户端如果缓存了旧的路由信息会收到MOVED重定向被迫刷新。这个过程通常在毫秒级完成但如果客户端没有正确处理 MOVED就会持续报错。这是应用层最常见的坑之一后面会专门讲。5.2 旧主回归自动降级为从节点原主节点恢复后会发现自己的 configEpoch 比新主低于是自动降级为从节点跟随新主复制数据。这里有个重要细节旧主上如果有新主没有的写入故障期间产生的这些数据会被丢弃因为复制是单向的、以新主为准。这就是 Redis Cluster 故障转移的异步复制本质——它保证的是可用性和最终一致不是强一致。故障瞬间未同步的写入会丢失。如果你的业务不能接受任何丢失需要在应用层做补偿或者用WAIT命令做同步复制但会牺牲性能。5.3 手动故障转移CLUSTER FAILOVER 的正确用法除了自动转移Redis 还提供了手动命令CLUSTER FAILOVER在从节点上执行。它有三个模式CLUSTER FAILOVER默认模式需要主节点同意会先做数据同步再切换最安全。CLUSTER FAILOVER FORCE主节点不可达时强制切换可能丢数据。CLUSTER FAILOVER TAKEOVER完全绕过协商直接接管最危险仅用于灾难恢复。日常做计划内维护比如升级主节点我强烈建议用默认模式它能保证切换前数据同步完成几乎零丢失。TAKEOVER只在集群已经不可用、需要强行恢复时用。6. 实战排查为什么我的集群没有自动切换6.1 排查思路按阶段逐层定位遇到主挂了但没切换别慌按下面的顺序查确认主节点是否真的被标记 FAILcluster nodes看状态是fail?还是fail。确认从节点是否在线且数据够新看从节点的master_link_status和 offset 差距。确认多数主节点是否存活如果挂的主超过半数永远切不了。确认总线端口连通性telnet或nc测port10000。看日志从节点日志里搜Start of election、failover auth能看到选举卡在哪。6.2 常见问题速查表现象可能原因排查方法解决主挂了不切换存活主节点不足半数cluster nodes数主节点增加主节点或恢复故障节点切换后数据丢失异步复制未同步对比 offset应用层补偿或调小 timeout切换特别慢timeout 过大或从节点过多看选举日志时间戳调小 timeout减少从节点集群一直 fail总线端口不通测 port10000放行防火墙/安全组从节点不参选数据太旧看 validity-factor 计算调大 factor 或修复复制反复切换网络抖动误判看 PFAIL 频率调大 timeout6.3 独家避坑技巧技巧一用CLUSTER COUNT-FAILURE-REPORTS看故障报告数。这个命令能告诉你某个节点收到了多少份 PFAIL 报告是判断是否接近客观下线的利器。如果报告数在涨但没到多数说明网络在抖动。技巧二监控cluster_state和cluster_slots_pfail指标。前者是集群整体状态后者是处于 PFAIL 的槽数。这两个指标能提前预警比等到业务报错再查强得多。技巧三定期演练故障转移。别等真出事才验证。用CLUSTER FAILOVER或直接 kill 一个主节点观察切换时间和数据一致性。演练过的集群真出事时你心里有底。技巧四客户端必须处理 MOVED 和 ASK。很多切换后业务报错其实是客户端没刷新路由。用支持 Cluster 的客户端如 Jedis Cluster、Lettuce、redis-py-cluster并确保开启了拓扑刷新。7. 参数调优与容量规划让故障转移真正可靠7.1 核心参数对照与推荐值参数默认值作用推荐值说明cluster-node-timeout15000判定 PFAIL 的超时5000~10000内网可小跨机房调大cluster-replica-validity-factor10从节点数据新鲜度容忍倍数2~5越小越优先新数据cluster-migration-barrier1主节点最少从节点数1~2影响从节点自动迁移cluster-require-full-coverageyes槽未全覆盖是否停止服务视业务no 可提升可用性cluster-replica-no-failoverno从节点是否禁止转移noyes 用于特殊维护cluster-require-full-coverage这个参数值得单独说。默认yes时只要有任何一个槽不可用整个集群拒绝所有请求。改成no后只有受影响槽的请求失败其他槽正常服务。对可用性要求高的业务建议设为 no代价是部分数据可能暂时不可访问。7.2 主从数量与故障域的规划前面反复强调多数主节点这个约束。规划时记住主节点数量建议 3、5、7 这样的奇数容错数分别是 1、2、3。每个主节点配 1~2 个从节点且从节点要分布在不同物理机/机架/可用区。避免所有主节点在同一台物理机否则机器一挂全完。我见过一个反面案例6 个主节点全在同一批虚拟机上结果宿主机故障6 个主同时挂集群彻底不可用。故障域分散不是口号是保命线。7.3 监控指标清单想让故障转移可靠这些指标必须监控cluster_state集群状态ok 才正常。cluster_slots_assigned已分配槽数应等于 16384。cluster_known_nodes已知节点数异常下降说明有节点失联。cluster_size主节点数量变化说明发生了转移。各节点master_link_status从节点复制状态up 才健康。各节点master_last_io_seconds_ago复制延迟过大影响参选资格。把这些指标接入告警配合日志里的failover关键字基本能做到故障转移发生即感知。8. 写在最后几个我踩过的真实教训关于 Redis Cluster 故障转移我最后再分享几个用血换来的经验。第一别迷信自动。自动故障转移能处理大部分场景但网络分区、多数派失效这类情况它无能为力。关键业务要有降级预案比如本地缓存兜底、限流保护。第二切换时间不是越短越好。我早期把 timeout 调到 3 秒结果一次网络抖动触发切换切换过程中又抖动来回切了三次业务抖动比不切还严重。后来调到 8 秒稳定多了。稳定比快更重要。第三演练要常态化。我们后来定了个规矩每季度做一次故障转移演练随机 kill 一个主节点记录切换时间和数据丢失量。坚持一年后真出事时团队反应速度完全不一样。第四客户端配置和集群配置一样重要。再好的集群客户端不刷新路由、不重试、不处理 MOVED照样报错。上线前一定要压测客户端的故障恢复能力。这套东西说起来不复杂但每个细节都可能在关键时刻掉链子。希望这篇能帮你把 Redis Cluster 的故障转移从黑盒变成透明盒下次告警响起时你能胸有成竹地定位问题而不是像我第一次那样手忙脚乱。