ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PolarDB只读节点不可用?完整排查链路与止血恢复实践

PolarDB只读节点不可用?完整排查链路与止血恢复实践 大能人系列开年第一篇就是“丢人”。节后上班第一天钉钉群里报警机器人连发三条PolarDB集群 prod-order 的一个只读节点状态变成了“不可用”。紧接着业务群里就开始刷屏“订单查询超时”“后台列表转圈”所有走只读 endpoint 的流量全部卡住。我盯着控制台上那个红色的节点标识心里只有一个念头开年第一周就从节点不能用开始这个兆头属实有点背。但也正是这次事故让我把 PolarDB 从节点不可用的整条排查链路彻底走了一遍。这里先把结论放在前面在 PolarDB 这种共享存储架构下从节点不可用并不等于数据丢失多数时候是计算节点侧“追不上日志”或者存储链路抖动导致的处理顺序和传统 MySQL 主从架构完全不同。这篇文章就把我这次的完整过程、每一步的判断逻辑、以及最终怎么止血和预防的一次说清楚。对正在用 PolarDB 的 DBA、运维和开发同学应该能帮你在下次遇到同类告警时少走弯路。1. 开年第一场事故现场只读节点红灯业务侧一片超时1.1 PolarDB 从节点的角色和这次故障的表象先说下 PolarDB 的基本架构。它和传统 MySQL 主从复制最大的区别在于存储层是共享的。主节点和只读节点共用一份分布式存储底层叫 PolarFS计算节点本身没有独立的数据副本。只读节点要做的事情本质上就是不停地拉取主节点产生的 redo 日志然后并行回放到自己的内存里让用户的读请求能读到最新数据。所以 PolarDB 的从节点承载的是读流量扩展、读写分离、以及高可用切换时的数据支撑。这次出问题的集群是 prod-order主节点在可用区 A两个只读节点分别在可用区 A 和可用区 B。报警的是 r1 节点也就是和主节点同可用区的那个只读节点。故障表象很典型控制台节点状态显示“不可用”不是“运行中”也不是“重启中”。只读 endpointSLB 后面的只读地址持续返回连接超时报错是ERROR 2013 (HY000): Lost connection to MySQL server during query。主节点完全正常读写流量没受影响。另一个可用区 B 的只读节点 r2 没问题读流量没有全部挂掉但压力瞬间翻倍。这里有个容易误判的点很多人看到多个只读节点就以为“摘掉一个还有另一个”但只读 endpoint 的负载均衡策略并不会自动感知节点健康状态。如果你把大量慢查询或大查询压在 r1 上r1 不可用后连接池里那些指向它的长连接会持续报错直到连接被断开重建。所以不能只靠“还有另一个节点”来兜底监控和连接治理必须跟上。1.2 开年就“丢人”我先做了哪些“看起来对但可能错”的动作说实话刚看到报警时我的第一反应是先去控制台看节点状态然后准备直接点“重启”。这是很多 DBA 的肌肉记忆——MySQL 从库挂了重启一次不行就重建重建不行就重新搭。但 PolarDB 的从节点不一样它的数据文件在共享存储上重启只读节点只是让计算进程重新拉起来然后从头开始回放 redo 日志。如果根因是存储链路抖动或者日志回放参数有问题重启完了很可能过一会儿又挂。我这次强行忍住了没立刻点重启而是先看了监控曲线这一个决定后来证明是对的。因为从监控上看r1 节点的 CPU 在故障前 10 分钟开始飙升到 90% 以上IOPS 也出现了一个明显的尖峰然后才状态异常。这种“先有资源耗尽、后节点不可用”的顺序说明问题大概率出在计算节点自身而不是存储故障。如果盲目重启最多临时恢复根因还在。2. 从节点不可用的根因分层先分清是“假死”还是“真挂”2.1 不可用状态的几种常见形态PolarDB 只读节点不可用其实可以细分成几种形态每种形态对应的处理方式完全不同。我整理了一下自己踩过和见过的情况形态典型表现常见根因假死hang节点状态还是“运行中”但连接全部超时CPU 100%慢查询堆积、连接数打满、并行回放争抢资源日志回放积压节点能连上但主从延迟持续增长查询读到很旧的数据redo 回放速度跟不上写入速度、回放参数不合理进程 crash状态直接变“不可用”error log 里有 crash 信息OOM、内核 bug、某些参数设置触发异常存储链路抖动控制台提示存储节点异常IO 读写报错PolarFS 底层存储节点故障或网络抖动管控面操作导致状态显示“升级中”“迁移中”卡住小版本升级、规格变更、跨可用区迁移这次遇到的是“进程 crash 存储链路抖动”叠加在一起的情况属于最麻烦的那一类因为现象和根因不像单纯的 OOM 那么好判断。2.2 分层排查模型计算、复制、存储、网络、管控我的习惯是把从节点不可用问题拆成五个层面去排查每层都先看一眼再决定往哪层深挖计算节点层进程是否还活着内存是否耗尽有没有被 OOM killer 杀掉。复制层redo 日志位点落后多少并行回放 worker 是否卡住有没有报错。存储层PolarFS 的读写延迟是否升高IO 是否有超时或错误。网络层只读节点和主节点之间、计算节点和存储之间的链路是否正常。管控层是否有人在故障前后做过变更比如改参数、升规格、迁移可用区。这次排查的顺序我走的是“监控曲线 → error log → 复制状态 → 存储健康度”结果发现真正的问题藏在复制层和存储层的交叉点上。2.3 一个反直觉的结论从节点不可用比主节点故障更容易被忽略说个可能让很多刚接触 PolarDB 的人没想到的事从节点不可用的直接影响往往不是“读不可用”而是“读延迟不可控”。r1 挂掉之后应用侧的读流量不会自动全部切到 r2而是先重试几次超时后连接池才慢慢把连接摘除。这段时间里用户体验就是“一会儿能查一会儿不能查”看起来像是网络问题。另外如果这个集群只有一个只读节点那么 r1 不可用等于整个只读链路瘫痪所有读流量都会回落到主节点主节点压力骤增可能引发连锁故障。所以从节点的监控不能只看“状态是否运行中”还要看“延迟是否在可接受区间内”。3. 完整排查链路从监控告警到日志定位的实操过程3.1 第一步控制台和监控指标先给故障“画像”故障发生后我第一时间打开 PolarDB 控制台的节点监控页。重点看四个指标CPU 使用率、内存使用率、IOPS、主从延迟。这里有一个关键技巧不要只看平均值要把时间粒度调到 1 分钟甚至 5 秒才能看清故障前后的变化趋势。我这次看到的情况是故障前 15 分钟r1 的 CPU 从 30% 突然飙升到 92%。同时间段磁盘读 IOPS 冲到 8000 以上平时只有 2000 左右。主从延迟指标在故障前 3 分钟开始出现“暂无数据”这通常意味着节点已经处于异常状态监控采集不到。这个画像基本可以排除“网络分区”和“管控面变更”两个方向因为没有做任何变更操作而且如果有网络分区r2 也可能受影响。重点转向计算节点资源耗尽和日志回放问题。3.2 第二步error log 和系统日志里找直接线索PolarDB 的 error log 可以从控制台的日志管理里下载也可以登录到只读节点上查看本地日志文件。我这次先看的是 error log 的最后 500 行找到了两段关键信息。第一段是 OOM 相关[ERROR] InnoDB: Cannot allocate memory for the buffer pool [ERROR] InnoDB: Error in allocating buffer pool第二段是 redo 回放中断[ERROR] InnoDB: Redo log replay failed due to IO error [WARNING] InnoDB: Page read from storage timed out结合监控里 CPU 和 IOPS 的双双飙升基本可以拼出故事了某个时间点 r1 上跑了一个大查询把内存和 CPU 资源大量占用buffer pool 分配失败触发了 InnoDB 的错误处理同时因为这个大查询产生了大量物理读 IO导致 PolarFS 存储链路出现 IO 超时。redo 回放的线程发现读页面超时直接按错误处理退出节点进入异常状态。这里顺便分享一个排查经验OOM 不一定是真的“内存不够”也可能是 buffer pool 分配瞬间被大查询打满再加上 redo 回放线程也在争抢内存。看 dmesg 可以确认 Linux 内核有没有杀进程但 PolarDB 是云上托管形态很多时候你拿不到 dmesg需要从 error log 的上下文推断。3.3 第三步复制状态 SQL 明确“落后多少”PolarDB 兼容 MySQL 协议所以可以使用熟悉的复制监控 SQL。在只读节点上执行如果还能连上的话或者连到主节点看从节点的状态SHOW REPLICA STATUS\G重点看这两个字段Seconds_Behind_Source主从延迟秒数。Replica_IO_Running和Replica_SQL_RunningIO 线程和 SQL 回放线程的状态。我这次在 r1 挂掉后尝试连接已经连不上了所以是通过控制台的“延迟监控”看到的历史数据来推断故障前 5 分钟延迟还在 0 秒故障前 1 分钟突然跳到 120 秒然后节点状态变成不可用。这说明 redo 回放在短时间内积压了大量日志随后回放线程因为 IO 错误退出。如果节点还能连上另一个很有用的查询是看并行回放 worker 的状态SELECT WORKER_ID, LAST_APPLIED_TRANSACTION, LAST_APPLIED_TRANSACTION_END_TIME FROM performance_schema.replication_applier_status_by_worker;正常情况下能看到多个 worker 并行工作且每个 worker 的LAST_APPLIED_TRANSACTION_END_TIME都接近当前时间。如果某个 worker 的状态长时间不变基本就是回放卡住了。3.4 第四步存储健康度确认“底层有没有问题”PolarDB 的控制台里一般不直接暴露 PolarFS 的细粒度指标但可以通过两个间接方式来确认观察其他节点比如 r2在同时段的延迟和 IOPS 是否异常。如果只有 r1 出现 IO 超时更可能是 r1 自身的资源问题导致存储请求排队而不是存储整体故障。如果 r2 也同时出现延迟升高说明问题可能出在存储层需要提工单让阿里云后台介入检查 PolarFS 节点。我这次的判断倾向于“r1 自身资源导致 IO 排队”因为 r2 在故障时段整体平稳延迟一直保持在 3 秒以内。所以重点放在计算节点侧的优化和恢复而不是上报存储故障。4. 快速止血与恢复验证哪些操作稳妥有效4.1 止血手段的选择重启只读节点 vs 主备切换在 PolarDB 里从节点不可用后的止血手段主要有两个重启只读节点、主备切换。我的建议是能重启就不要优先切主。原因在于 PolarDB 的存储是共享的。重启只读节点只是重新拉起计算进程然后从共享存储里读取数据页并回放 redo 日志整个过程比传统 MySQL 从库重建快得多通常几分钟内就能恢复。而主备切换会改变集群的角色应用连接会断开而且一旦切换原来主节点上的长事务和连接都会中断影响面更大。这次我选择的方法是先在控制台上对这个只读节点执行“重启”操作同时临时把只读 endpoint 里这个节点的权重调低如果使用了自定义 endpoint避免一恢复就立刻被大流量打满。4.2 实际操作步骤和注意事项具体操作步骤如下在 PolarDB 控制台的“集群列表”里找到 prod-order点击“节点详情”。确认 r1 节点当前状态为“不可用”记录下故障时间点。点击“重启”按钮选择“立即重启”。等待节点状态从“重启中”变为“运行中”这个过程我这次用了大约 4 分钟。重启完成后不要马上放量先手动连上去验证。连接验证时我使用了下面的 SQL 检查环境是否正常SELECT version, port, server_id; SHOW STATUS LIKE wsrep%; -- 不需要PolarDB 不是 Galera SHOW REPLICA STATUS\G注意 PolarDB 是共享存储架构重启只读节点时不需要也不应该执行类似CHANGE MASTER TO的操作它不会像传统 MySQL 从库一样去“重新拉取全量数据”。如果某个同事下意识打出了RESET SLAVE请立刻拦住他。4.3 回放积压的临时处理先追日志再放流量这里有个实操中很容易踩的坑只读节点重启后会先进入“追赶日志”的状态此时节点看起来是运行中的但主从延迟可能还有几十秒甚至几分钟。如果立刻把流量切过去用户会读到比较旧的数据而且大量读请求会拖慢回放速度。我这次的做法是重启完成后先在控制台观察延迟监控看到主从延迟降到 5 秒以内再把自定义 endpoint 里的节点权重加回来。如果业务上允许部分旧数据阈值可以放宽到 30 秒但最好把告警阈值设成 10 秒提醒自己关注。另外如果延迟一直降不下来可以考虑调整 PolarDB 的日志回放参数。这类参数一般叫“并行回放线程数”或者“replay worker 数”在参数配置页面可以修改。我的建议是先调大并发回放数观察 5 分钟如果延迟开始下降则继续等待如果延迟没变化再考虑把只读节点上的大查询全部 kill 掉给回放线程让路。4.4 验证恢复是否彻底别只看状态“运行中”节点状态变成“运行中”只代表进程活着不代表它真的能扛住业务流量。我这次做了四步验证用只读 endpoint 执行SELECT 1确认连接正常。跑一条业务典型查询比如订单列表查询确认返回结果正常且耗时在历史范围内。观察 10 分钟内的主从延迟曲线确保没有再次飙升。检查 error log 是否有新的 OOM 或 IO 超时记录。当这四步都通过后才把完整的读流量切回 r1。这一步千万不要跳我就是因为在类似场景下偷懒少做了第二步结果恢复后跑了半小时又出现延迟抖动。5. 让从节点长期稳定配置、告警与巡检建议5.1 参数配置给回放线程和资源预留合理空间这次故障的直接诱因是大查询打爆了内存和 IO所以我后来把 r1 的配置做了一次针对性调整把慢查询日志打开并设置long_query_time 2方便及时发现问题 SQL。如果频繁出现大查询优先考虑拆分只读节点职责比如让一个只读节点专门跑报表另一个只读节点服务线上查询避免混合流量互相干扰。连接数上限不要设置得太高同时要设置合理的max_execution_time给查询加一个最大执行时间兜底防止一条慢 SQL 长时间占用资源。这里有个技术点需要说明PolarDB 的只读节点回放 redo 日志时和用户的查询是共享 CPU 和内存资源的。如果某个只读节点上跑了一个全表扫描的大查询它会把 buffer pool 里的热点数据页挤出去导致回放线程需要从存储层重新读页IO 上升延迟飙升。所以只读节点的资源规划不能只看 QPS还要看“慢查询的并发度”。5.2 告警规则的合理设计延迟和状态要分开盯我这次的教训之一是原来自带的告警规则太粗糙只配置了“节点状态异常”和“CPU 超过 90%”没有配置主从延迟的细粒度告警。结果故障发生时第一个报警来自业务群而不是监控平台响应时间至少慢了 10 分钟。建议至少配置三类告警告警类型阈值建议触发后的动作只读节点状态变化状态 ! 运行中立刻人工介入主从延迟超过 30 秒持续 1 分钟检查回放状态和慢查询只读节点 CPU超过 85%持续 5 分钟排查慢查询和连接数阈值可以根据业务容忍度调整但核心原则是延迟告警要比状态告警更敏感。很多从节点故障的苗头都是延迟先升高状态后变化早发现 10 分钟就能省掉一次业务事故。5.3 定期巡检三分钟手动检查清单最后分享一个我每次巡检 PolarDB 集群时的三分钟检查清单都是命令和操作可以直接抄连到每个只读节点执行SHOW REPLICA STATUS\G确认Seconds_Behind_Source是否为 0 或接近 0。在控制台看一眼只读节点的 CPU 曲线是否存在周期性尖峰如果有找出对应时间点的慢查询。检查只读 endpoint 下挂的节点权重和健康检查配置确保故障节点能被自动摘除。如果使用了连接池比如 RDS Proxy 或自建的 ProxySQL确认连接池和 PolarDB 的字符集、事务隔离级别等配置一致避免连接重建引发额外问题。每周做一次只读节点重启演练验证从节点在共享存储架构下恢复的速度是否符合预期。这个巡检清单看起来简单但真的能帮你在故障发生前发现大部分隐患。比如我这次如果再早一点检查延迟曲线其实可以从 CPU 尖峰和 IOPS 升高提前半小时发现异常完全有机会在业务受到影响前手动把流量摘掉。写在最后开年“丢人”后的几点体会复盘这次从节点不可用我最大的体会是在 PolarDB 这种共享存储架构里遇到从节点故障千万不能用传统 MySQL 的思维去处理。不要一上来就重建从库不要盲目执行RESET SLAVE优先做的是分层排查、重启恢复、延迟验证三步走。另一句话想送给同行告警和监控才是真正的“大能人”。这次问题能比较快定位不是因为我多聪明而是因为恰好看了正确的监控指标。如果你的 PolarDB 集群还没有配置主从延迟和只读节点状态的细粒度告警建议今天就补上别等到下一个“开年第一案”再来后悔。
返回列表