ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Redis集群全集群不可用?常见原因与生产环境排查指南

Redis集群全集群不可用?常见原因与生产环境排查指南 凌晨两点半手机警报把我从床上拽起来整个业务的 Redis 集群不可用了所有读写都在报 CLUSTERDOWN。等我赶到机房发现其实只是某个分片的主节点和从节点同时挂了但整个集群却真的“全军覆没”。你可能和我当年一样想不通Redis 集群方案不是号称高可用吗一个分片故障为什么剩下的节点也一起“罢 工”今天就好好聊一聊究竟哪些场景会让 Redis 集群方案从局部故障演变成全体不可用以及我在生产环境里踩过坑之后总结出来的排查思路和预防策略。这篇文章主要面向正在做 Redis 集群运维、或者准备在生产环境上线 Redis Cluster 的同学。我会把导致全集群不可用的常见原因拆开讲配套给出真实的排查案例和参数建议希望能帮你少走一点弯路。1. 先说清楚一个分片挂了为什么全业务都跟着不可用1.1 决定全集群不可用的关键开关cluster-require-full-coverage很多人第一次接触 Redis Cluster 时都会以为它是天然“分片容灾”的某个分片挂了其他分片继续提供服务只是业务损失一部分数据。但实际情况往往不是这样因为 Redis Cluster 里有一个特别容易被忽略的配置项cluster-require-full-coverage。这个参数默认是yes含义是只有集群内所有的哈希槽都被正常节点覆盖时集群才会对外提供读写服务。任何一段槽点无人接管整个集群就会进入CLUSTERDOWN状态。你可以把它理解成一辆公交车车上每一站都必须有司机的车门只要有一扇车门打不开整辆车就拒载哪怕是其他所有车门都正常。所以当某个分片的主节点和从节点全部不可用时这个分片原来负责的那段哈希槽就成了“无人认领”的空槽。只要cluster-require-full-coverage还保持默认的yes整个集群就会从“部分不可用”直接升级成“全体不可用”。1.2 槽缺失后的客户端视角CLUSTERDOWN 与 connection refused当集群进入这种状态后客户端拿到的不是单个 key 的MOVED重订向也不是某个分片超时而是所有命令直接返回类似这样的错误CLUSTERDOWN The cluster is down如果你用的是高可用连接池比如 JedisCluster 或 Lettuce这种错误会非常快地暴露成业务异常甚至引发熔断。很多业务方会第一时间认为是“所有 Redis 节点全挂了”但登录服务器一查大多数节点进程还活着CPU、内存都正常只是拒绝执行命令而已。我前几年处理过一个线上事故就是这个场景业务团队为了省资源把 3 主 3 从的 Redis Cluster 部署在 3 台机器上每台机器放一个主节点和一个从节点。某天其中一台机器因为硬件故障宕机主节点和对应的从节点同时没了结果这台机器上的分片所负责的槽全部缺失。当时cluster-require-full-coverage是默认的yes于是整个 Redis 集群立刻进入CLUSTERDOWN所有业务请求在几秒钟之内全部失败。所以第一点必须记住如果你的集群用的是默认配置不要天真地以为只有“所有主节点全体宕机”才会让集群不可用只要任意一个分片的主从全挂全集群都会跟着不可用。后面讨论的所有“全集群不可用”很大一部分都是这个开关在底层放大问题。2. 物理层级的全挂主从同机、机房断电与批量运维误操作2.1 主从同机部署托管机房常见的高危姿势前面那个案例其实暴露了一个很典型的部署问题主从同机部署。很多运维同学会觉得主从只要能做数据备份就行把主节点和它的从节点放在同一台物理机上省机器、省带宽内网复制还快。但一旦这台物理机宕机主从同时失联分片就没有任何节点可以接管槽位直接出现空洞。这种“鸡蛋放在同一个篮子里”的部署方式本质上是把高可用变成了高风险的假象。虽然 Redis 集群本身有从节点提升机制但前提是从节点真的“活着”且能被其他主节点感知到。如果主从在同一个物理机上物理机断电、网卡故障、内核死机都会让从节点和主节点一起消失提升机制根本来不及发挥作用。就算不是同机部署很多托管 IDC 的场景里也有类似问题两个节点放在同一个机柜机房级别的跳闸或者网络割接会同时干掉一整排机器。如果主节点和从节点分布在相邻机架或同一接入交换机下造成的效果和同机部署几乎一样。2.2 批量操作与全量重启一次小心翼翼的教训除了物理故障运维误操作也是让整个集群所有节点不可用的高发原因。我见过不少团队在集群扩容、缩容或者升级版本时采用了“先全部关掉再一个个启动”的做法。比如为了统一换配置通过自动化平台在凌晨对集群所有节点执行了systemctl restart redis。理论上 Redis 集群在节点重启后会自动恢复但如果所有节点同时重启集群总线里的节点互相发现和握手需要时间再加上某些节点的 RDB 持久化恢复很慢就会在短暂窗口里出现大量节点互相标记为PFAIL最终触发故障迁移风暴。更危险的是直接对所有节点执行类似清理缓存的命令比如FLUSHALL或者DEBUG SLEEP。我曾经遇到过一次线上事故有人写脚本的时候搞错了节点范围把集群所有主节点都执行了FLUSHALL结果所有分片的数据全部清空。这不叫节点不可用但站在业务角度整个集群确实“不可用了”——所有缓存命中全部失效数据库被流量洪峰瞬间打崩。再提醒一个容易忽略的点如果集群所有节点都部署在同一台物理机上跑 Docker一旦宿主机物理故障整个集群会全部消失。这种部署方式在测试环境无所谓但千万别在生产环境用。3. 网络分区才是最阴险的集群杀手3.1 多数派机制为什么关键节点被隔离后反而更糟要说哪种场景最容易让 Redis 集群方案全线崩溃我觉得不是简单的宕机而是网络分区network partition。网络分区是指集群中的节点被切成了几个互不相通的“岛屿”每个岛屿内的节点之间仍然通信正常但岛屿之间完全无法互相访问。Redis Cluster 的节点故障判定和主从切换都依赖大多数主节点的协商机制。比如一个 3 主 3 从的集群如果发生网络分区后某一边只有 1 个主节点和它的从节点另一边有 2 个主节点和从节点。这时候如果主节点正好在“少数派”那一侧少数派是否能把从节点提升为主取决于它们能不能联系到“大多数主节点”并获得投票。答案是否。在 Redis Cluster 中从节点触发主从提升必须获得超过一半的主节点投票。少数派网络里只有一个主节点它无论如何也达不到“超过一半”的门槛于是这个分片永远无法完成主从切换。更糟糕的是少数派网络里的旧主还活着它可能还在对外提供写入服务。等网络恢复时旧主发现自己变成了孤岛数据也会被同步对新主覆盖写入的数据直接丢失。这就是网络分区最阴险的地方它不会立刻把集群状态标记成FAIL甚至在分区持续期间客户端如果恰好连接到了少数派侧的旧主表面上一切正常但数据实际上已经处于“待丢失”状态。而如果旧主无法对外服务集群状态又因为槽点缺失进入CLUSTERDOWN整库不可用。3.2 脑裂与写入丢失旧主在新主诞生后依然“提供服务”脑裂这个词大家应该不陌生。简单说就是同一份数据分片在网络分区后出现了两个主节点各自接收写入最终数据无法合并。在哨兵模式Sentinel下脑裂的典型过程是这样的主节点与一部分哨兵失联但和客户端还能通信。哨兵集群中满足投票条件的部分哨兵以为主节点挂了于是执行故障转移把一个从节点提升为新主。此时旧主仍然在接受客户端写入但新主也在从旧主同步数据注意如果网络是单向丢包旧主可能接收不到来自哨兵的CONFIG REWRITE或者写操作同步不及时等网络恢复后旧主会尝试切换为新主的从节点并把自己分区期间写入的数据丢掉。站在业务视角这段时间并不是“集群不可用”而是“静默数据丢失”。但对于一些强一致场景这种丢失比不可用更致命。脑裂发生后你经常会看到监控图上出现主节点写入量和新主写入量同时上涨等到恢复完一查部分 key 的计数器被回退了。3.3 选举失败导致的槽位无主在 Redis Cluster 中选举失败除了因为少数派网络还有另一个常见原因分片内所有从节点的数据都太旧了。Redis 从节点发起提升时必须满足一定的“数据新鲜度”条件也就是从节点和主节点的数据滞后不能太远。如果某个分片的从节点因为长期网络波动或磁盘慢复制积压严重那么在故障发生后它没有资格发起选举。结果就是主节点宣告失败但从节点又提升不起来这个分片的槽就彻底没人接管了。我遇到过一台机器磁盘性能严重下降主节点写入很慢但还在勉强服务从节点的复制持续积压。等主节点最终被判定为故障时从节点已经落后了太多选举直接被跳过槽点缺失整个集群再次进入CLUSTERDOWN。所以不要以为只要配了从节点就万能了还要盯住主从复制的两点指标master_link_status和master_repl_offset之间的差值。4. 参数配置踩雷心跳超时与选举让不可用悄无声息4.1 cluster-node-timeout 设得太小引发误判连锁cluster-node-timeout是 Redis Cluster 的节点超时判定参数默认 15000 毫秒。它影响的是当一个节点在超过该时间内没有响应集群总线的心跳 PING其他节点就会把它标记为疑似故障PFAIL并在更大范围内扩散。这个参数如果设得太小比如只有 3000 毫秒在生产环境里非常容易触发误判。举一个我亲眼见过的例子。线上某个业务集群在高峰期会偶发长时间的 GC 停顿单次停顿可以达到 4~5 秒。当时cluster-node-timeout配了 3000 毫秒于是在 GC 停顿的那几秒里正执行任务的节点没有回应心跳立刻被其他节点标记为PFAIL。紧接着由于集群总线自身也会因为网络抖动失去部分心跳多个节点几乎同时被互相标为PFAIL从而触发了大规模的主从切换尝试。这种瞬间的选举风暴会让整个集群的状态反复横跳客户端一会儿连到新主一会儿又遇到旧主表现就是各种超时和CLUSTERDOWN。有人可能会问那把这个超时调得很大比如 60 秒是不是就安全了也不是。太大了会导致真正的故障被延迟很久才被发现用户会先经历一段很长的不可用时间。所以这个参数需要在业务容忍度和节点稳定性之间取一个折中。我个人的建议是从默认值 15 秒开始结合业务和网络的实际抖动情况调整到 30 秒左右比较稳妥。4.2 分片主从全挂后选举需要满足哪些条件聊到选举就必须把条件说透。在 Redis Cluster 中一个分片要完成从节点向主节点的提升要满足以下几点从节点必须有资格竞选也就是它的复制偏移足够接近原主节点。从节点必须能在cluster-node-timeout加上一定容错时间内联系到大部分主节点并从它们那里拿到投票。一次故障转移周期内只允许一个从节点成功当选。如果这些条件中的任意一个不满足分片就没办法自动恢复。所以在设计高可用架构时光有从节点还不够你还得关注从节点是否是“健康的从节点”。4.3 主从复制里的 min-slaves-to-write 如何让“半挂”变成“全挂”除了 Cluster 模式很多人其实还在用更简单的主从复制加哨兵方案或者干脆就是裸的主从复制。这个场景里有个参数特别坑min-slaves-to-write。min-slaves-to-write的作用是当主节点发现连接的从节点数量低于配置的阈值时拒绝执行写命令。它是为了保证主从数据一致性的防止主节点和所有从节点失联后继续写数据造成大量数据无法复制。但如果你把这个参数配成了 1而集群本来只有一个从节点那么一旦从节点出现故障或者复制断开主节点就会直接拒绝所有写入。从节点的角度是“一个从表没了”从业务的角度却是“整个集群写不进去了”。这种“半挂”状态最容易被误认为是整个集群不可用。我处理过一次客服告警业务反馈 Redis 写入大面积失败查了半天数据库连接正常、内存也正常最后发现是从节点所在机器内存不足被 OOM 杀掉主节点上的connected_slaves变成 0于是所有写入被min-slaves-to-write直接拦下来了。所以配置高可用时一定要想清楚一个问题你是要“可用性优先”还是“一致性优先”如果是可用性优先建议把min-slaves-to-write保持默认的 0。如果确实是强一致场景宁可让集群限写也不要写丢那就必须接受“从节点全挂等于不可写”这种结果。5. 一次全集群不可用的完整排查复盘5.1 告警与初步现象所有应用同时报错这一节我完整还原一次“全集群不可用”的排查过程你可以把它当作排查手册用。告警来的时候通常是监控平台把 Redis 集群的cluster_state打到了fail然后大量应用开始报CLUSTERDOWN The cluster is down。这时候第一反应不要慌先抓住两个信息这个不可用是不是只有某一段槽的命令失败还是所有命令都失败当前有多少节点还在正常工作多少节点已经离线。先用redis-cli连上一个还活着的节点执行redis-cli -h 192.168.1.10 -p 6379 cluster info关注两个字段cluster_state:fail和cluster_slots_pfail。如果cluster_state显示fail基本可以确定存在未被覆盖的哈希槽并且cluster-require-full-coverage是yes。然后执行redis-cli -h 192.168.1.10 -p 6379 cluster nodes它的输出会列出每个节点的 ID角色master/slave连接状态connected/disconnected以及是否被标记为fail。排查的时候优先找出哪些master节点状态是fail或disconnected以及这些主节点对应的slave节点现在是什么状态。5.2 状态检查用 cluster nodes 快速定位问题分片举个例子假设输出里有这样一行a1b2c3... :00 slave 9f8e7d... 0 1700000000 0 connected 0-5460如果它的主节点 ID9f8e7d...对应的记录是9f8e7d... 192.168.1.20:637916379 slave,fail,noaddr 0 1700000000 0 disconnected那就说明这个分片的主节点已经因为失联而判 fail但它的从节点还在线。这种情况下理论上集群应该会尝试提升这个从节点。如果从节点迟迟没有被提升就需要去看具体原因了。再使用redis-cli -h 192.168.1.10 -p 6379 cluster slots查看每个哈希槽的范围都分配给了哪些节点。如果某个槽范围的输出里既没有主节点也没有从节点或者只有noaddr状态说明该槽点已经没有任何节点接管这就是整个集群进入fail的直接原因。到这里你就能把问题缩小到一个具体的分片它持有的哪一段槽对应的主从节点现在都在哪里。5.3 从日志到网络找出真正的根因定位到具体分片之后下一步是找出“为什么主从都没有接管槽”的根因。这时候要看两类日志一类是 Redis 节点自己的日志。不同版本日志位置不一样通常位于/var/log/redis/redis-server.log。重点关注有没有类似下面的内容... # Cluster state changed: ok - fail ... # Node ... is now a slave without a master ... # Failover auth denied: ...如果看到Failover auth denied说明从节点尝试发起选举但被拒绝了原因可能是拿不到大多数主节点的投票或者数据偏移太大不够资格。另一类是系统层面的网络或性能日志。可以用dmesg -T看内核有没有报网卡丢包、软中断超时用top看 Redis 进程的 CPU 和内存用redis-cli info stats看latest_fork_usec之类的耗时指标。很多时候问题就藏在某台机器的一个长时间 RDB 快照 fork 上fork 过程中 Redis 进程可能无法及时响应集群总线心跳导致被误判为故障。5.4 恢复操作与验证恢复操作取决于根因。如果只是某个主节点崩溃但从节点还活着最直接的办法是手动触发故障转移redis-cli -h 192.168.1.30 -p 6380 cluster failover注意cluster failover通常需要连接到一个从节点如果你想让这个从节点接替它的主节点可以连上去执行这个命令。它有两种形态FORCE是强制让从节点提升为当前主节点不考虑复制偏移量TAKEOVER则更暴力会忽略所有一致性检查并强制选为 master。除非紧急情况否则不要轻易用TAKEOVER。如果从节点也不在线那么只能把好的节点重新加入集群或者把一个其他分片的多余节点重新分配做从节点。具体操作是先把节点启动起来然后用redis-cli cluster meet重新握手再用redis-cli cluster replicate master_id重新设置从属关系。等槽位都被新的主从接管后最后确认cluster_state:ok然后观察一段时间看看是否有新的PFAIL事件。整个恢复里最容易被忽略的一步是恢复后要重新检查各分片主从的部署位置。如果旧主当时和旧从在同一物理机上新加入的从节点就别再放回去了否则下次宕机你还会经历一遍同样的全集群不可用。6. 不想再遇到这种事架构设计与运维习惯上的建议6.1 部署层面跨机架、跨机房与合理的从节点最简单的道理往往最容易被忽略主从一定要跨部署单元。这里的部署单元可以是物理机也可以是机柜、机架。条件允许的话三机房或者两机房三中心的架构更好。副本数量方面我建议生产环境的 Redis Cluster 至少做到“一主一从”核心集群做到“一主二从”。为什么要多一个从不仅仅是防单点从节点还能在主节点故障后、第一个从节点也临时不可用时提供兜底。尤其是当你把cluster-require-full-coverage保留为yes的时候一个分片只有单从一旦从节点出问题全集群不可用的风险就非常高。部署时还要注意把同一个分片的主从节点放到不同的故障域里。如果你用云主机至少是不同的可用区如果是自建机房至少要放到不同的机柜和接入交换机下。我见过不少事故主从在同一个接入交换机下交换机升级或光模块故障一个分片的主从一起告警紧接着就是全集群CLUSTERDOWN。6.2 配置层面full coverage 取舍与超时参数的经验值关于cluster-require-full-coverage我需要重点说一句这个参数没有绝对的对错只看你的业务能否接受部分数据不可用。如果设置成yes那么任意分片故障全集群会进入拒绝服务状态。好处是一致性好坏处是可用性差。如果设置成no那么某个分片故障时其他分片的读写仍然能够正常进行只是落到故障分片上的 key 会报错。对电商、内容型应用来说很多场景可以容忍一部分缓存 miss 或者一部分请求失败这种取舍更合适。我的实践建议是如果业务没有强一致要求或者对部分请求失败有兜底方案生产环境可以直接设成no避免单个分片故障演变成全集群雪崩。但要注意设置成no之后监控要额外关注每个分片是否健康因为你不会看到全局CLUSTERDOWN这种明显报警了而是会看到某些 key 的命中率异常下降。超时参数方面我给出的参考值是cluster-node-timeout 3000030 秒同时把cluster-slave-validity-factor调整为 5~10 之间的一个值。这个参数表示从节点多久没和主节点同步就失去选举资格太小了可能让从节点在短暂网络恢复后无法提升太大了又会选出数据过于陈旧的从节点。建议线上下发前做一次混沌测试人为杀主节点看选举时长是否在可接受范围内。6.3 运维层面危险命令管控、灰度操作与定期演练最后聊点运维习惯。工具类操作对 Redis Cluster 的破坏力往往被低估我建议至少在权限上把如下命令列为高危险FLUSHALL、FLUSHDB、DEBUG SLEEP、CLUSTER RESET、CLUSTER FORGET、CONFIG SET。这些命令要么能直接清掉数据要么能破坏集群元数据。对集群做批量操作时无论是一次本地升级还是重启都尽量做到按分片灰度先操作一个分片等集群状态稳定下来再操作下一个分片。千万别对全体节点同时执行重启脚本。Redis Cluster 确实能做到自动发现和恢复但恢复也需要时间如果所有主节点同时重启分片之间互相等待握手很容易在启动窗口里出现状态异常。还要定期做“杀主演练”和“断网演练”。杀主演练是验证从节点能否快速提升断网演练是模拟网络分区场景看看少数派主节点会不会错误地继续接受写入。很多团队等到线上出事才第一次看到CLUSTERDOWN长什么样那体验确实不太美好。我在实际生产环境里跟 Redis 集群打了这几年交道最大的一个体会是绝大多数不可用其实不是“被敌人打败的”而是“自己配置给自己挖的坑”。从cluster-require-full-coverage到物理机部署、再到心跳超时每一个看起来不起眼的配置都可能在关键时刻把局部问题放大成全集群灾难。你不需要精通每个极端场景但至少要知道哪个开关会让整个集群瞬间躺平然后针对自己业务的取舍把它调到合理的状态。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进