ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Redis集群为何会整体不可用?五大陷阱与救火指南

Redis集群为何会整体不可用?五大陷阱与救火指南 先给结论Redis 集群并不是“只要还有主节点活着就能用”。在默认配置下只要有一个槽位失去所有者整个集群就会直接进入CLUSTERDOWN状态所有读写全部被拒绝——一个分片的主从全挂能拖垮整个集群。这不是玄学而是集群设计从一开始就写死的“要么完整要么全挂”的一致性偏好。这篇文章结合我多年维护和救火 Redis Cluster 的实际经验把会让集群整体不可用的场景一个个列出来每个场景配套对应的日志、报错特征和恢复办法适合正在用或者准备上线 Redis 集群的同学收藏。1. 先搞清楚集群的“可用”是怎么定义的1.1 槽位、复制、投票三件事Redis Cluster 的高可用不是靠“哪个节点活着”来判断的它依赖三个底层机制槽位hash slot整个集群有 16384 个槽位按 key 的 CRC16 哈希值映射每个主节点负责一段槽位区间。只要有一个槽位没有被任何节点负责集群状态就是不一致的。复制replication每个主节点可以有多个从节点从节点实时复制主节点数据在主节点挂掉后可以接管它负责的槽位。投票voting主节点之间通过 gossip 协议互相发心跳默认cluster-node-timeout是 15000ms。一个节点超过这个时间没收到另一个节点的心跳会先把它标记为PFAIL主观下线当集群内多数主节点都认为它是PFAIL才升级为FAIL客观下线。从PFAIL到FAIL这个设计非常关键它保证了“判定死亡”不是单个节点拍脑袋而是需要集群多数节点共同确认从机制上防脑裂。当某个主节点确认失败后它的从节点会发起FAILOVER_AUTH_REQUEST其他主节点投票谁的数据偏移量最新谁接替。但这个选举有个硬前提必须拿到多数主节点的投票。票凑不齐从节点就永远升不了主。1.2 “整个集群不可用”其实分两个层次很多人理解的不可用是“节点全挂了”但 Redis 集群最常见、也最坑的一种不可用是节点明明活着集群却整体拒绝服务。此时你连一个热 key 都读不出来返回的全是(error) CLUSTERDOWN The cluster is down这就是cluster_state: fail的状态。触发它的条件有两个存在没有节点负责的“孤儿槽位”节点认为自己和集群多数节点已失联无法形成有效决策。这里有个核心参数cluster-require-full-coverage默认是yes。它的含义就是只要有任何一个槽位失去归属集群直接进入 fail 状态所有客户端请求全部拒绝。等于说整个集群的可用性被最差的那个分片拽着走。明白了这层逻辑下面讲的所有“集群整体不可用”场景都会归根到这两个机制上。2. 什么情况会让集群全盘拒绝服务2.1 主节点失联超过半数投票选不出新主这是最直观的场景假设集群有 5 个主节点某次交换机故障导致 3 个主节点同时失联。剩下的 2 个主节点无法凑齐“多数票”所有失联主节点的从节点都发起不了有效的 failover。于是这 3 个主节点负责的槽位全部变成孤儿槽位默认配置下整个集群CLUSTERDOWN。这里容易有个误解很多人以为“挂了 3 个主还剩 2 个主能服务”但实际上 Redis 集群的可用性不是按“剩余节点数”算的而是按“槽位覆盖完整性”和“选举多数”算的。主库大面积死亡后没有多数票从库无法自动接管即使部分槽位还有从库活着槽位依旧处于“没有有效主节点”的状态cluster-require-full-coverage yes下所有读写被一刀切。换句话说“主节点故障超过半数”不是唯一的触发条件任何让槽位失去主从覆盖的场景都能触发全局不可用。2.2 某一组槽位的主从全部挂掉槽变成“孤儿”这是生产环境最容易被低估的风险。只要一次物理机宕机恰好把某个分片的主节点和它所有从节点一起带走哪怕其他分片全部健康整个集群也会因这一个分片的孤儿槽位而进入 fail 状态。我用实际案例说明之前有一个 3 主 3 从的集群为了省机器把其中一组主从放在了同一台物理机上。某天那台机器磁盘损坏主和从一起没了。存活的两个分片运行正常Redis 却对全集群所有读写返回CLUSTERDOWN。业务方反馈“整个缓存突然全挂了”实际上只挂了一台物理机。这就是require-full-coverage的残酷性它把 16384 个槽位看成整体一个分片的完整性等于整个集群的完整性。少一个槽位等于全挂。2.3 网络分区把集群切成两半集群最怕的不是单点故障而是网络分区尤其是把节点切成了“大小两个区”。假设 6 主 6 从跨两个机房部署骨干网抖动把两个机房切断了。A 机房有 4 个主节点B 机房有 2 个主节点A 机房侧因为拥有多数节点可以正常形成决策B 机房的主节点会被 majority 标记为 FAILA 机房内的从库可以发起接管B 机房侧只有 2 个主节点永远凑不齐 3 票无法进行任何选举几个主节点一旦需要 failover对应的槽位就迅速变成孤儿槽位。结果往往是A 机房正常服务B 机房因为 sees 多数不可达加上孤儿槽位直接进入 fail 状态。如果客户端路由策略不当业务请求在 B 机房一侧全部报CLUSTERDOWN表现为“整个集群不可用”。这个场景没有完美的解药。Redis 集群的多数派机制决定了一件事它宁可让一部分节点不可用也不允许脑裂后两边同时产生新主写入数据。对很多团队而言“分区期间一边挂掉”是可以接受的真正不能接受的是两边同时写、数据分叉。2.4 级联失败才是最常见的“整集群事故”真正导致 Redis 集群整体瘫痪的往往不是一次“精准打击”而是级联失败。最典型的是下面这条链某主节点发生长耗时 GC 停顿或磁盘 IO 抖动心跳超过cluster-node-timeout其他节点把它标记为 PFAIL随后多数确认 FAIL多个从节点同时对它发起 failover其中一个升主新主节点负载飙升同时触发持久化和复制积压又出现心跳超时新一轮 FAIL 继续扩散多个分片进入 failover 循环。我在一次事故里看到过cluster-node-timeout被人从默认的 15000 调到了 3000理由是“加快故障切换”。结果一次全集群 GC 停顿 4 秒所有节点互相标记为 PFAIL一堆从库同时尝试 failover集群在十几秒内彻底 fail。把超时调得太小等于把误判成本放到最大。2.5 配置型事故timeout 太小、bind 错、时钟漂移除了真实的故障还有一类“人祸”也会让整个集群不可用而且这类问题排查起来特别费劲cluster-node-timeout设置过小任何一次突发延迟都可能引发全集群误判多个分片同时 failovercluster-announce-ip/ bind 配置错误在容器或虚拟机环境里节点间通过错误的 IP 互相发现心跳断断续续集群长时间处于 PFAIL/FAIL 边缘状态极其不稳定服务器时钟漂移Redis 集群虽然不强制要求 NTP但时钟不同步会直接影响心跳超时判断和选举纪元A 节点认为 B 已经超时B 却认为自己一切正常vote 混乱误删nodes.conf这个文件记录了节点 ID 和集群拓扑重启时如果文件被误删节点会以为自己是个新节点拿不到原来的槽位信息集群关系直接错乱。配置型事故里nodes.conf被误删是我见过最容易造成“全网瘫痪”的操作直接导致槽位归属信息丢失集群无论如何都无法恢复只能重建或从备份恢复。3. 日常最容易踩的坑和配置取舍3.1 require-full-coverage一个参数决定全局生死这个参数是“整个集群不可用”话题里绕不开的开关必须单独说。yes默认任何槽位裸奔全集群拒绝服务no槽位裸奔时访问到裸奔槽位的请求报CLUSTERDOWN其他槽位照常服务。很多团队会把no当作保命配置我理解这个选择但也想提醒它的问题设为no之后你必须要接受“部分请求报错”这个常态。如果监控只盯“集群节点存活”不盯“具体哪些槽不可用”很容易出现大量请求失败却没人发现的情况。我的建议是线上核心链路能承受短时全停的保持默认yes配合完善的告警不能承受全停、宁可部分降级的设no但必须对cluster_state:fail和具体孤儿槽位做告警无论哪种都应该在架构上保证“每个分片的主从不在同一台物理机/同一个可用区”否则require-full-coverage怎么配都救不了。提示cluster-require-full-coverage是 rolling 配置修改后逐节点生效但要在所有节点上保持一致否则节点间互相判定集群状态不一致。3.2 持久化 fork 阻塞触发“心跳失联”Redis 执行BGSAVE或 AOF rewrite 时要 fork 子进程。如果实例内存很大fork 瞬间会阻塞主进程可能长达几秒甚至十几秒。所有节点同时在做定时持久化的话很可能集体错过心跳窗口互相标记 PFAIL/FAIL。之前有个客户集群内存 80GB 实例把save配置设成了每 5 分钟执行一次结果高负载时段 fork 阻塞 8~10 秒恰好大于cluster-node-timeout整集群进入 failover 风暴。应对办法很简单把cluster-node-timeout调到 15~30 秒之间给 GC 和 fork 留出缓冲错峰持久化别让所有节点同一时刻 BGSAVE监控 fork 耗时和redis-cli info stats里的latest_fork_usec超过阈值就要排查内存和内存碎片化问题。3.3 迁移/缩容中途宕机做 resharding 或者缩容时如果中途有节点宕机事情会非常难看。槽位迁移依赖MIGRATE命令数据迁移到一半时源节点和目标节点处于“migrating/importing”的中间状态。这个状态下如果源主节点挂了而它的从节点没有同步到最新的迁移状态槽位可能处于一种“既有人认领又没人真正负责”的模糊状态。这种情况最容易踩的坑是不看 cluster info 就开始操作集群变更。我的习惯是操作前先跑一遍redis-cli --cluster check确认cluster_slots_ok 16384且没有 PFAIL/FAIL 节点再动拓扑。变更过程中严格避免同时进行另一项故障操作。3.4 热 key 把单点打满引发的连锁Redis 集群没有全局负载均衡key 落在哪个分片是哈希决定的。某个热 key 所在的分片被打满 CPU 或内存后这个节点的响应变慢心跳开始超时被标记为 FAIL从库接管后继续被打满再次超时……这种“热 key 导致单点故障单点故障触发全局 fail”的链路本质上和 2.4 的级联失败是同一种模式。唯一的解法是在业务层做热点 key 拆分或者在架构上把热点 key 的访问分散到多个 key 上减少单分片压力。集群本身的故障转移机制救不了“被打满”的节点因为打满它的流量还在。4. 哨兵方案和集群方案的边界4.1 哨兵模式什么时候全挂很多团队没上 Cluster用的是主从加哨兵。哨兵模式下整集群不可用的触发条件和 Cluster 不太一样哨兵多数派消失假设部署 3 个哨兵任何一个哨兵宕机都不影响但如果 2 个哨兵同时宕机剩下 1 个哨兵即使发现主库挂了也无法完成 failover需要多数派确认和选举 leader。此时主库宕机写入直接中断服务不可用。down-after-milliseconds设得太短哨兵对主库的误判也会导致频繁 failover甚至主库还在等服务却已经切换走写入全部被拒。从库被误删或者复制断裂哨兵能发现主库挂掉但从库数据延迟过大failover 后接手的从库缺少大量数据业务读取到的是很久以前的数据这种“伪可用”比不可用更可怕。哨兵方案的核心保护参数是min-replicas-to-write旧版本叫min-slaves-to-write。设置为主库至少有 N 个健康从库时才接受写入可以在极端情况下保护数据一致性代价是可用性下降。生产上到底设多少本质上是业务写多还是读多、能容忍丢失多少数据。4.2 两种方案“不可用”的条件对比我整理了一张对比表方便你直接根据自身架构判断风险场景Redis Cluster哨兵 主从单主节点宕机从库自动接管槽位仍覆盖哨兵多数派存活时自动切换某一分片主从全挂槽位失控默认全集群 CLUSTERDOWN该主从链路不可用其他链路不受影响超过半数管理节点故障无法选举槽位多个失控全局失败哨兵无法构成多数无法切换写入中断网络分区多数派一侧可继续少数派一侧 fail哨兵分区主库可能长期不切换或双主风险脑裂保护选举投票机制天然防脑裂靠 quorum min-replicas 约束风险更高一句话总结Cluster 用“全局槽位完整性”换来了强一致和高分片能力但也因此把单分片故障的影响面放大到全局哨兵方案各主从链路相对独立反而没那么容易被“一粒老鼠屎”拖垮整个集群。这也是为什么很多中小团队到现在还坚持用哨兵而不是 Cluster。5. 出事后怎么定位和恢复5.1 三行命令先看现场集群进入未知状态时第一件事千万别重启节点先做现场采集。我救火的一贯顺序是redis-cli -h ip -p port cluster info redis-cli -h ip -p port cluster nodes redis-cli -h ip -p port cluster slotscluster info里的关键指标cluster_stateok还是failcluster_slots_assigned/cluster_slots_ok两者不等说明有孤儿槽位cluster_known_nodes确认节点总数是否和预期一致。cluster nodes输出里每一行代表一个节点重点看中间 flags 列myself当前节点自己master/slave角色fail?PFAIL代表某个节点认为它有主观故障嫌疑failFAIL已经多数确认客观下线disconnected该节点无法和集群内其他节点正常通信。这三个命令能快速回答两个问题集群是怎么挂的孤儿槽位还是多数派失联以及哪些节点处于什么状态。5.2 恢复顺序和止血操作确认了故障类型后按下面的顺序处理先救孤儿槽位。优先启动宕机的主节点或从节点让槽位恢复归属。节点启动后会自动根据nodes.conf里的拓扑信息重新加入集群不需要手动CLUSTER MEET。凑不齐多数时用 FORCE 接管。如果超过半数主节点失联导致无法自动选举可以在存活的从节点上执行redis-cli -h replica-ip -p replica-port cluster failover forceFORCE可以绕过多数票机制让从库直接接管。注意这属于紧急操作只有在你确认原主节点短期无法回来时才建议用否则会出现双主风险。节点恢复但数据不对时不要直接上线。原主节点重新启动后它可能会带着一份“旧数据”回到集群。cluster-replica-validity-factor默认值是 10意思是主节点失联超过 10 个cluster-node-timeout后从库不再自动让它变回主节点避免陈旧数据污染集群。如果非要恢复优先把它作为从库挂回集群让它先同步新主数据再切换角色。配置层面止血。紧急情况下可以先把cluster-require-full-coverage临时改为no让集群恢复部分可用把大面积故障的影响面收缩到受害分片。但这个操作只能救“孤儿槽位”场景救不了“选举多数派丢失”场景。注意cluster failover force是高危操作。执行前必须确认要接管的从库具备最新的复制偏移量否则你恢复的是一个数据严重落后的“假主节点”。5.3 集群可用性排障速查下面这张表是我自己压箱底的排障速查遇到问题直接对照报错信息典型场景恢复方向CLUSTERDOWN The cluster is down存在孤儿槽位或多数派失联启动死节点、检查槽位覆盖、必要时 force failoverCLUSTERDOWN Hash slot not served某个槽位无主可依多半是 require-full-coverage no 状态下的局部不可用恢复该分片主从MOVED slot ip:port客户端用了旧路由信息使用支持自动路由的客户端或刷新集群拓扑ASK slot ip:port槽位正在迁移的中间状态客户端处理 ASK-REDIRECT或等待迁移完成MASTERDOWN Link with MASTER is down哨兵模式下主库挂掉且从库拒绝旧数据读取触发或手动执行哨兵 failoverLOADING Redis is loading the dataset节点重启加载 RDB/AOF 中等待加载完成检查持久化文件是否过大OOM command not allowed when used memory节点达到 maxmemory写入被拒扩容、清理 key、调整淘汰策略5.4 关于告警和演练的小建议还有一个值得单独拿出来的经验集群不可用的恢复不一定靠“对”的操作更多时候靠“快”的响应。如果你没有提前做故障演练等到线上真的出现CLUSTERDOWN再去看日志、查节点状态业务早就超时到爆炸了。我们团队内部会定期做三类演练随机杀主节点验证从库能否在预期时间内接管模拟网络分区观察两端分区的 failover 行为是否符合预期演练过程中把cluster-node-timeout、require-full-coverage等关键参数逐个调整验证监控告警是否真的能触发。不做演练的集群配置再漂亮也只是纸面高可用。6. 最后说点运维层面的体会这几年的经验里我最深的感受是Redis 集群的整体不可用几乎都不是“设计缺陷”而是“配置和部署方式没跟上它的预期”。它默认认为每个分片的主从物理隔离、心跳网络干净、节点时钟一致、节点数不会频繁变动。你在哪个环节破了它它就在哪个环节拉爆全局。cluster-node-timeout别调到 5 秒以下require-full-coverage别在没搞清数据一致性代价时随意改节点变更前记得先看cluster info跨机房部署一定要确认多数派在一个区内。这些都不是新知识但每一条都是从真实的线上事故里摔出来的。如果你现在正在规划集群方案我的建议是先把“容忍哪部分不可用”想清楚再决定用什么方案、调什么参数。高可用从来没有银弹只有你愿意为哪一种故障买单的选择。
返回列表