Redis 集群故障切换卡住:没有副本被升主

主节点挂了但没有副本接管,集群停在 fail 状态。通过检查主节点 quorum、FAIL 标记、副本资格,必要时手动 takeover 来修复。

Redis 集群掉了一个主节点,你预期 15 到 30 秒内会有副本接管。几分钟过去集群还是坏的:CLUSTER INFO 显示 cluster_state: failCLUSTER NODES 里挂掉的主被标成 master,fail、它的副本还是 slave,那个主负责的 hash slot 范围里每个 key 都返回 CLUSTERDOWN The cluster is down 或者 MOVED/TRYAGAIN。这就是故障切换卡住了。

最快的修法: 先确认多数主节点还活着并且能互相通信(3 主集群至少要 2 个可达),再确认那个存活副本的数据是新的,然后在该副本上跑 CLUSTER FAILOVER FORCE。如果老主已经确认死亡、而且你已经丢了主节点 quorum,那 CLUSTER FAILOVER TAKEOVER 才是最后手段。本文剩下的部分讲的是在你动用 TAKEOVER 之前怎么找到真正的原因——因为在这里走错捷径会导致脑裂和数据丢失。

本文针对的是真正的 Redis Cluster(cluster-enabled yes),不是 Sentinel。这两套是不同的产品、修法也不同——下一节帮你五秒钟分清自己用的是哪一套。

先分清楚 Cluster 和 Sentinel

Redis 有两套完全不同的高可用方案,修法也不一样:

  • Redis Cluster(本文)把数据分片到多个主节点,靠 gossip 协议自己处理故障切换。没有 Sentinel 进程。由副本发起选举,由多数主节点投票。诊断命令是 CLUSTER INFOCLUSTER NODES
  • Redis Sentinel 是一组单独的 sentinel 进程,监控一个不分片的主从组,靠 replica-priority 和 Sentinel quorum 工作。诊断命令是 SENTINEL MASTERSSENTINEL SLAVES

如果你跑 redis-cli -p 26379 SENTINEL MASTERS 能拿到响应,那你用的是 Sentinel 而不是 Cluster,相关配置是 replica-priority 和每个主的 quorum。下面全部假设你用的是真正的 Redis Cluster(节点以 cluster-enabled yes 启动)。再强调一次:replica-priority 只有 Sentinel 会读,在 Cluster 模式下被忽略——设了它对集群故障切换没有任何作用。

集群故障切换到底怎么走

搞清楚机制就知道它会卡在哪。截至 2026 年 6 月(Redis 7.x 和 8.x),流程是:

  1. 某节点不再应答 ping。超过 cluster-node-timeout(默认 15000 毫秒)后,发现它的节点把它标成 PFAIL(疑似故障)并 gossip 出去。
  2. 多数主节点都把同一个节点报成 PFAIL,它才升级为 FAIL。到这一步才可能开始切换。
  3. 挂掉主的每个合格副本算一个很短的选举延迟(500ms + random(0-500ms) + rank * 1000ms,rank 0 是复制偏移量最高、也就是数据最新的副本),然后向各主请求投票。那个固定的 500 毫秒是为了给 FAIL 状态留出传播时间,免得在多数主还没认这是 FAIL 时就发起投票;随机部分让各副本错开;rank 保证最新的副本先开口。
  4. 当某副本在同一 config epoch 内拿到多数主的票就胜出。3 主集群要 2 票,5 主集群要 3 票。胜出者拿到一个更高的新 configEpoch,接管挂掉主的 slot。

任何破坏第 2 步(没有多数主看到 FAIL)或第 4 步(没有多数主能投票)的情况,都会让你卡在 fail 状态,副本永远是 slave 升不了主。准确的规则见 Redis Cluster 规范

常见原因

按造成卡住的频率排序。

#原因一句话排查FAIL 标记有没有?
1丢了主节点 quorum(太多主挂了或被分区)CLUSTER INFOcluster_known_nodes 在,但可达的主更少没有
2主卡在 fail?(PFAIL),没升到 failCLUSTER NODES 显示 fail? 而不是 fail没有
3挂掉的主压根没有副本CLUSTER NODES 里没有指向它的 slave不适用
4副本落后太多(超过有效期窗口)INFO replication 里链路断的时间超过有效期窗口
5副本被 cluster-replica-no-failover yes 挡住CONFIG GET cluster-replica-no-failover 返回 yes
6新副本还没被多数主认识在每个主上跑 CLUSTER NODES 都没列出这个副本
7cluster-require-full-coverage yes 掩盖了恢复(集群拒绝所有写)CONFIG GET cluster-require-full-coverage 返回 yes

1. 丢了主节点 quorum

这是最常见的真实原因。集群故障切换需要多数主节点,不是看节点总数。加副本永远改变不了这个算法。3 主集群里如果有 2 个主挂了或被分区,剩下那 1 个达不到多数,任何副本都升不了主。

怎么判断:CLUSTER INFO。如果 cluster_state:fail,而从一个健康节点出发 3 个主里只能连到 1 个,你就是丢了 quorum。先恢复到其他主的连通性;不要想着对每个分片同时 TAKEOVER 来”修”这个问题。

2. 主卡在 fail?

被标成 fail? 的节点是被怀疑了(PFAIL),但多数主还没确认它为 fail。在升到 fail 之前不会开始选举。这通常发生在只有一个主能看到故障时(不对称分区)。

怎么判断: CLUSTER NODES 显示 master,fail? 而不是 master,fail。查一下为什么其他主仍觉得它可达(单向防火墙规则、安全组、集群总线端口上的 MTU/丢包)。

3. 挂掉的主没有副本

不存在的东西没法升主。如果故障的主一个副本都没有,集群就直接丢掉那些 slot。

怎么判断:CLUSTER NODES 里有没有 slave <挂掉主的 id> 行。没有就是没有候选。

4. 副本落后太多

副本只有在数据足够新时才合格。有效期窗口是 cluster-node-timeout * cluster-replica-validity-factor / 1000 秒。用默认值(cluster-node-timeout 15000cluster-replica-validity-factor 10)就是 15 * 10 = 150 秒。链路断的时间超过这个的副本不会自己升主。

怎么判断: 在副本上跑 INFO replication。看 master_link_statusmaster_link_down_since_secondsmaster_last_io_seconds_ago。如果断线时间超过有效期窗口,它就被判为不合格。

5. 副本被显式禁止切换

cluster-replica-no-failover yes 让副本永远不自动升主。如果挂掉主的所有副本都设了这个,自动切换就发生不了。

怎么判断: 在每个副本上 CONFIG GET cluster-replica-no-failoveryes = 被挡。(再说一遍:replica-priority 是 Sentinel 的设置,在这里没作用。)

6. 新副本还没被各主认识

副本只有在被多数主认作副本之后才能当选。在故障前一刻才加进来的节点,可能还没传播开。

怎么判断: 在每个主上跑 CLUSTER NODES(或 CLUSTER REPLICAS <主-id>),确认这个副本出现了,再去依赖它。

7. cluster-require-full-coverage 掩盖了恢复

设成 yes(默认)时,只要有任何一个 slot 没被覆盖,整个集群就拒绝命令,于是连健康的分片看起来都像挂了。它本身不挡切换,但会把局部故障看成全面故障,逼你仓促 TAKEOVER

怎么判断: CONFIG GET cluster-require-full-coverageyes + 某 slot 范围没覆盖 = 整个集群返回 CLUSTERDOWN

动手前先确认

  • 确认是真卡住,不是只是慢。正常的集群切换大约要 cluster-node-timeout 再加几秒(默认约 15 到 20 秒,最多 30 秒)。
  • 找出哪个(些)主是 fail,以及受影响的 hash slot 范围(一共 16384 个)。
  • 记下应用里哪些 key 前缀打不通。
  • 改任何东西之前先抓状态:至少 3 个节点的 CLUSTER NODESCLUSTER INFO,以及每个副本的 INFO replication
  • 留好回滚方案:动集群之前先做快照(BGSAVE 或拷一份 RDB/AOF)。

分步修复

Step 1:确认 FAIL 标记和主节点 quorum

# 在一个健康节点上
redis-cli -h <healthy-node> -p 6379 CLUSTER INFO
redis-cli -h <healthy-node> -p 6379 CLUSTER NODES

CLUSTER NODES 里,挂掉的主应该显示 master,fail。如果显示 master,fail?,说明集群还没确认这次故障(原因 #2),问题出在主与主之间的连通性,而不是副本。数一下你到底能连上几个主:在任何切换能完成之前,必须有多数主活着。

Step 2:恢复集群总线和主与主之间的连通性

集群节点用两个端口通信:客户端端口(6379)和集群总线端口,后者是客户端端口加 10000(16379),除非用 cluster-port 覆盖。每对节点之间这两个端口都必须开放。

# 从每个节点到其他每个节点,测两个端口的可达性
redis-cli -h <other-master> -p 6379 PING
nc -vz <other-master> 16379    # 集群总线端口

# 检查防火墙规则
iptables -L -n | grep -E '6379|16379'
# AWS:确认安全组在所有节点子网之间放通 6379 和 16379

# 例:总线端口被挡就放通
ufw allow from <cluster-subnet> to any port 16379 proto tcp

总线端口被挡是主卡在 fail?、永远到不了 fail 的典型原因:客户端能用,但 gossip 和投票走不通。

Step 3:确认存在一个合格副本

# 在某个主上,列出挂掉主的副本
redis-cli -h <master> -p 6379 CLUSTER REPLICAS <dead-master-node-id>

# 在候选副本上,检查新鲜度和链路状态
redis-cli -h <replica> -p 6379 INFO replication
# 关注:
#   role:slave
#   master_link_status:down
#   master_link_down_since_seconds
#   slave_repl_offset

如果一个副本都没列出来(原因 #3),就没法切到任何东西,只能加节点重建这个分片。如果链路断的时间超过有效期窗口(原因 #4),副本被判为不合格——要么等它重新同步,要么把窗口调大:

# 全集群放宽资格窗口(有效期 = node_timeout * factor / 1000)
redis-cli -h <node> -p 6379 CONFIG SET cluster-replica-validity-factor 20
# 设成 0 完全关闭新鲜度检查(接受一个旧副本)

Step 4:解封那些主动退出切换的副本

# 在挂掉主的每个副本上检查
redis-cli -h <replica> -p 6379 CONFIG GET cluster-replica-no-failover

# 如果返回 "yes",重新打开自动升主
redis-cli -h <replica> -p 6379 CONFIG SET cluster-replica-no-failover no
redis-cli -h <replica> -p 6379 CONFIG REWRITE

集群模式下别去动 replica-priority——它只有 Sentinel 会读,在这里被忽略。

Step 5:手动强制故障切换

如果主已死、主与主之间能互通、又有一个数据新的副本但还是没升主,就在副本上触发:

# 在选好的副本节点上跑
redis-cli -h <replica> -p 6379 CLUSTER FAILOVER FORCE

三种模式,从保证最弱到最强:

  • CLUSTER FAILOVER —— 跟老主协调以避免数据丢失。需要老主可达,所以主死了就用不上。
  • CLUSTER FAILOVER FORCE —— 跳过跟(不可达的)主握手,但仍需多数主授权新 epoch。这是主崩溃时的正常选择。
  • CLUSTER FAILOVER TAKEOVER —— 完全跳过集群授权。副本单方面取下一个 config epoch 并接管 slot。只在你已丢主 quorum 且无法恢复时用,因为老主回来会脑裂。

只有下面三条都成立才用 TAKEOVER

  • 老主已确认死亡(不是只是被分区)。
  • 你无法在可接受的时间内恢复多数主。
  • 你接受丢掉那些还没复制出去的写入。

CLUSTER FAILOVER(不管带不带 FORCE)会立刻返回 OK,但不保证成功:按CLUSTER FAILOVER 官方文档的说法,它只是了一次切换。只有 TAKEOVER 是同步执行的。如果排了却一直没完成,去副本日志里查:

  • Manual failover timed out —— 副本等了几秒后放弃了。重新检查主 quorum 和总线端口。
  • Currently unable to failover: Waiting for votes, but majority still not reached —— 副本能连到各主,但拿不到票。这几乎总是主侧连通性问题(主与主之间总线端口被挡),或者这个副本还没被多数主认识(原因 #6)。

Step 6:验证升主

# 在原来的副本上
redis-cli -h <replica> -p 6379 ROLE                 # 现在应打印 "master"
redis-cli -h <replica> -p 6379 INFO replication     # role:master

# 从任意节点
redis-cli -h <node> -p 6379 CLUSTER INFO
# 期望:
#   cluster_state:ok
#   cluster_slots_assigned:16384
#   cluster_slots_ok:16384
redis-cli -h <node> -p 6379 CLUSTER NODES           # 升主的节点现在标 "master"

Step 7:决定 cluster-require-full-coverage

redis-cli -h <node> -p 6379 CONFIG GET cluster-require-full-coverage
# 让集群在局部故障时仍服务已覆盖的 slot
redis-cli -h <node> -p 6379 CONFIG SET cluster-require-full-coverage no
redis-cli -h <node> -p 6379 CONFIG REWRITE

权衡:写没被覆盖的 slot 仍会失败,但健康分片能继续服务,而不是整个集群返回 CLUSTERDOWN。读可用性优先选 no,严格写一致性优先选 yes

怎么确认已经修好了

  • 每个节点的 CLUSTER INFO 都显示 cluster_state:okcluster_slots_ok:16384
  • CLUSTER NODES 里升主的节点是 master,老主要么消失、要么变成 slave
  • 升主节点上 ROLE 打印 master
  • 应用能读写之前受影响 slot 范围的 key,没有 CLUSTERDOWN/MOVED 死循环。
  • 老节点回来重新挂成副本后,它上面的 INFO replication 显示 master_link_status:up,偏移量差距收敛。

长期预防

  • 至少部署 3 个主,分布在 3 个可用区,这样掉一个区还能保住主多数。(quorum 看主的数量,所以 3 主能容忍 1 个挂。)
  • 给每个主至少配一个副本,最好在不同区,让每个分片都有升主候选。
  • 所有节点之间同时开放客户端端口和集群总线端口(端口 + 10000);总线端口被挡是主卡在 fail? 上不去 fail 的头号原因。
  • 有意识地调 cluster-node-timeout。调小(比如 5000 毫秒)检测更快,但网络抖一下就可能误切;默认 15000 毫秒更保守。
  • 监控副本延迟和有效期窗口;当副本的 master_link_down_since_seconds 接近 cluster-node-timeout * cluster-replica-validity-factor / 1000 时告警。
  • 每月跑一次切换演练:杀一个主,确认自动升主在大约 cluster-node-timeout 再加几秒内完成。

容易踩的坑

  • 把 Cluster 当 Sentinel 用:调 replica-priority 或 Sentinel 的 quorum 对集群切换毫无作用。
  • 老主只是被分区(没死)就跑 CLUSTER FAILOVER TAKEOVER——它回来你就脑裂、写入分叉。
  • 忘了集群总线端口(默认 16379)。客户端连得上,但 gossip 和投票走不通,主永远卡在 fail?
  • CONFIG SET 之后忘了 CONFIG REWRITE——重启后改动就没了。
  • 所有主放一个 AZ——单区故障就跌破主多数,谁也切不了。

FAQ

主明明已经死了,为什么副本没自动升主? 最常见的是多数主无法确认这次故障(挂掉的主因为集群总线端口被挡卡在 fail?),或者你丢了主 quorum。自动升主需要多数先把节点标成 fail、再投票,跟你有多少副本无关。

CLUSTER FAILOVER FORCETAKEOVER 用哪个? 只要多数主还活着就用 FORCE,它能拿到正经授权的 config epoch。只有在你丢了主 quorum 且无法恢复、并且接受老主回来脑裂的风险时,才用 TAKEOVER

replica-priority 能控制集群里哪个副本胜出吗? 不能,那是 Sentinel 的设置。在 Redis Cluster 里数据最新的副本(rank 最低、复制偏移量最高)先发起选举;要彻底排除某个副本,就在它上面设 cluster-replica-no-failover yes

健康的切换应该多久? 大约 cluster-node-timeout(默认 15 秒)让节点到 FAIL,加上不到一秒的选举延迟,所以用默认值大概 15 到 20 秒走完,最多约 30 秒。超过一两分钟才算真卡住。

该不该调小 cluster-replica-validity-factor 调小会让切换更难(副本要更新才合格);调大或设成 0 让更旧的副本也能升主。缓存场景用更大值或 0 偏向可用性;会话/数据存储保持默认,宁可等重新同步。

副本日志报 Waiting for votes, but majority still not reached,怎么办? 副本在尝试当选,但各主不投票。副本能连到各主,但要么各主无法达成”老主是 FAIL”的共识(主与主之间总线端口被挡,挂掉的节点卡在 fail?),要么这个副本还没被多数主认识(原因 #6)。先修好主与主之间总线端口(默认 16379)的连通性,并确认这个副本在每个主的 CLUSTER NODES 里都出现了,再重试 CLUSTER FAILOVER FORCE

相关阅读

标签: #后端 #排查 #redis