十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从99.999999999%到“宰鱼”:高可用系统故障演练全流程拆解

从99.999999999%到“宰鱼”:高可用系统故障演练全流程拆解 最近运维群里流传一张截图很让人在意一个代号叫4nim0sity的测试集群完成了高可用切换演练通告末尾就一句“宰鱼了”。外行人看是玩笑但配合“99.999999999%”这个目标懂行的人会意识到这实际上是一场把系统可靠性按到极致的高强度故障演练。先说我的判断99.999999999% 这种级别的可用性绝对不是在机器上堆几个告警脚本就能实现的。它一定是靠预案、注入手段、验证手段和复盘机制把系统里最怕出的故障反复“宰”过之后才可能谈得上接近。而所谓的“宰鱼”在工程语境里通常指挑一个最容易出问题、影响最大的故障场景在受控环境下主动制造出来并验证系统能正确恢复。这既是稳定性工程的常规动作也是团队面对超低容错目标时最有效的准备方式。这篇文章不从趋势和概念出发直接拆解一套高可用系统的故障演练全过程。读完你会明白四个问题99.999999999% 在时间上到底意味着什么。“宰鱼”到底宰的是什么故障为什么要主动制造故障。一次完整的故障演练要准备哪些前置条件、脚本和验证手段。实战里最容易翻车的坑有哪些怎么排查。1. 99.999999999% 到底意味着什么99.999999999% 这个数字从表面看只是在高可用后面多挂几个 9但把它换算成真实时间结果非常震撼。可用性的定义很简单可用性 可用时间 /可用时间 不可用时间一般按全年 8760 小时计算。举个例子如果目标可用性是 99.9%那么一年最多允许宕机 8.76 小时如果目标是 99.999%一年最多允许宕机 5.26 分钟。下面是不同可用性目标对应的折算表可用性目标每年不可用时间说明99.9%8.76 小时很多内部系统实际水平99.99%52.56 分钟开始需要自动化运维99.999%5.26 分钟需要一定容灾设计99.9999%31.54 秒对变更和故障响应要求很高99.99999%3.15 秒需要自动切换机制99.999999%0.315 秒接近人工响应极限99.9999999%0.0315 秒必须全链路自动容错99.99999999%0.00315 秒对架构和监控要求极端苛刻99.999999999%0.000315 秒基本属于验证与理论结合的目标看到这里就明白了99.999999999% 换算下来全年允许不可用时间只有 0.000315 秒。人肉盯着告警去敲命令或者是靠“出事了再处理”的救火模式在这样量级的目标面前没有任何意义。这里要纠正一个误区不少团队会把配置了 MySQL 主从复制、再来一个 Keepalived 漂移就觉得自己已经“高可用”了。但高可用不仅仅是“主挂了能切换”而是从应用层到中间件再到数据库所有链路都能在极短时间内完成感知、决策、切换并且切换过程中不产生脏数据、不丢失请求。所以到了 99.999999999% 这个阶段测试的不是服务器而是整套系统的故障反应速度和恢复正确性。2. “宰鱼”在工程语境里指的是什么“宰鱼”是一个比喻翻译成技术行话就是故障演练也叫故障注入测试或混沌工程实践。它和传统的“压测”有本质区别。压测关心的是系统在超出预期流量的情况下表现如何通常往系统上加负载故障演练关心的是系统在关键依赖失效时能不能自动恢复。前者是“往肚里塞东西”后者是“往心脏上捅一刀再等自愈”。为什么需要主动故障注入原因很简单系统中很多隐藏问题只在故障发生那一瞬间才会暴露。举个例子。很多团队的生产环境都有 MySQL 主备。备份节点正常运行很久主备复制进程也没告警大家以为一切没问题。但有一次主库磁盘满导致实例异常退出切换过去才发现备库的数据因为复制中断已经落后主库一个多小时业务被迫回滚。类似的问题如果不通过主动演练提前暴露只会在最没有准备的时刻爆发。“宰鱼”这个名字之所以有味道是因为它把一件听起来很恐怖的事情——主动制造故障用更形象的方式描述出来了。好的故障演练不是无差别破坏而是有剧本、有预警、有回滚方案的“精准手术”。一次合格的“宰鱼”流程要走完这几步确定演练目标本次要验证哪个故障场景期望恢复时间是多少。检查前置条件确认是测试环境备份完整回滚方案可行。注入故障执行脚本或命令让故障真实发生。观察系统行为记录监控指标、告警、自动切换动作。验证结果确认业务恢复、数据一致、没有产生脏数据。恢复并复盘把故障消除恢复到初始状态整理改进项。这也是为什么运维群里那句“宰鱼了”看起来轻松实际上背后是整个预案体系的成熟体现。3. 一套最小高可用架构与前置条件要讨论故障演练就得先明确演练对象。为了方便描述下面以代号4nim0sity的最小高可用系统为例。这套系统不追求生产级别的复杂只保留高可用验证所必需的组件客户端 | v 负载均衡/网关HAProxy | |--- 业务节点 A |--- 业务节点 B | 数据库主备 |--- 主库 MySQL当前提供读写) |--- 备库 MySQL实时复制主库故障时提升这套架构中负载均衡提供请求分发和健康检查业务节点提供实际服务数据库主备保证数据的持久化能力。应用层故障时负载均衡会把流量切到健康节点数据库故障时需要由数据库高可用组件或脚本完成主备切换。演练前需要准备如下环境Linux 服务器至少 3 台一台装负载均衡两台跑业务节点和对应数据库实例。软件组件HAProxy、Nginx 或同类负载均衡MySQL 或同类型关系数据库Keepalived 或其他虚拟 IP 管理工具curl、systemctl 等基础命令。脚本运行环境Bash 或 Python 3。数据库权限一个具备备份和恢复权限的账号但必须使用最小权限原则不能用 root 到处跑。监控或日志收集至少能查看各节点的进程状态和服务日志。这里版本不写死。不同公司的系统和软件版本差异很大但演练思路是一致的。关键是明白环境越简单越能看清故障注入和恢复机制是不是真的有效。4. 演练前必须做备份与恢复检查很多人对“故障演练”的理解是直接去 kill 进程或者停掉数据库实例。如果是在生产环境里这样操作毫无疑问是一场事故。正确地做法是先在测试环境演练并且演练之前必须完成备份和恢复预检。故障演练的本质是产生可恢复的破坏。所以备份不是为了应付检查而是给自己留退路。下面提供一个简化的备份脚本核心作用是在演练前对数据库做一次一致性备份同时把主库的日志位点记录下来方便演练后对齐数据。#!/bin/bash # 文件路径scripts/snapshot.sh # 作用演练前对数据库做一致性备份并记录主库日志位点 set -euo pipefail BACKUP_DIR/data/backup TS$(date %Y%m%d%H%M%S) DB_USERbackup_user DB_PASS请替换为实际密码 mkdir -p ${BACKUP_DIR}/${TS} # 单事务模式做全量备份避免备份期间数据不一致 mysqldump \ --single-transaction \ --routines --triggers \ -u${DB_USER} -p${DB_PASS} \ --all-databases ${BACKUP_DIR}/${TS}/all_databases.sql # 记录当前主库日志文件与位置 mysql -u${DB_USER} -p${DB_PASS} \ -e SHOW MASTER STATUS\G ${BACKUP_DIR}/${TS}/master_status.txt echo 备份完成${BACKUP_DIR}/${TS}脚本本身的逻辑很直观--single-transaction保证 InnoDB 在备份期间读取到一致快照。--routines --triggers把存储过程和触发器一起备份避免恢复后丢失。master_status.txt记录主库 binlog 文件和坐标主备切换后用于比对或者修复复制关系。备份完成后还应该做一次“还原测试”。不然备份文件只有心理安慰真到恢复的时候才发现文件损坏或者权限不正确那就亏大了。测试环境里不必每次都恢复全量数据可以随机抽取一个库恢复确认能正常启动。5. 故障场景建模“鱼”应该挑哪条掌握了备份和安全边界下一步就是选择故障场景。很多人第一次做演练上来就停数据库看起来很大胆实际效果不一定好。因为故障注入越粗放出了问题时越难判断是预期行为还是新引入的问题。合理的做法是“由小到大、由应用到数据”分阶段建模。常见故障类型有故障类型典型场景对可用性的影响应用层故障业务进程死掉、OOM、假死部分请求失败或超时网络故障节点间断网、丢包、延迟升高依赖调用失败数据库故障主库实例宕机、复制中断数据不可写负载层故障负载均衡节点宕机入口不可用基础依赖故障DNS 异常、磁盘满、CPU 被打满整体性能下降一次演练不追求覆盖全部类型而是选择当前对可用性威胁最大的“那条鱼”。比如线上最近发生过进程 OOM那就优先演练 Killed 场景如果主库曾经因为大查询卡死那就重点演练数据库探活与切换。针对前面 4nim0sity 的最小体系比较标准的第一步是“业务节点故障”。这一步更容易观察负载均衡是否把请求转发到了健康节点也最容易验证来回切流量的链路是否正确。接下来用一个具体场景做完整演示业务节点 A 的进程被强制结束验证 HAProxy 自动把请求切到业务节点 B并且外部访问不中断。6. 完整示例从故障注入到自动切换6.1 健康检查脚本故障注入前必须先确定健康检查方式。HAProxy 默认可以通过 TCP 端口探测后端节点更推荐使用 HTTP 探测因为 HTTP 能同时验证进程存活和接口响应。先准备一个简单的健康检查脚本。#!/bin/bash # 文件路径scripts/health-check.sh # 作用周期性探测业务健康接口并打印状态码 HEALTH_URLhttp://127.0.0.1:8080/actuator/health TIME_OUT5 COUNT${1:-10} for i in $(seq 1 ${COUNT}); do code$(curl -s -o /dev/null -w %{http_code} \ --connect-timeout ${TIME_OUT} \ ${HEALTH_URL} || true) echo $(date %Y-%m-%d %H:%M:%S) code${code} sleep 1 done如果业务没有暴露健康检查接口可以先用端口探测代替nc -zv 127.0.0.1 8080健康检查是故障切换的输入源。如果探活设计得不合理可能出现节点已经假死但端口还通、或者端口正常但业务逻辑已经异常的情况进而导致切换不触发或误切换。6.2 配置负载均衡的健康检查HAProxy 的配置需要包含两个部分前端入口、后端节点池。下面是最小配置示例。global daemon maxconn 4096 defaults mode http timeout connect 5s timeout client 30s timeout server 30s frontend app_front bind *:80 default_backend app_nodes backend app_nodes balance roundrobin option httpchk GET /actuator/health server app-a 192.168.1.11:8080 check inter 2s fall 3 rise 2 server app-b 192.168.1.12:8080 check inter 2s fall 3 rise 2这里解释几个关键配置httpchk GET /actuator/healthHAProxy 每隔一段时间请求一次健康接口。inter 2s每 2 秒探测一次。fall 3连续 3 次失败才把节点标记为不可用。rise 2连续 2 次成功才把节点重新加入负载池。配置完成后执行haproxy -f /etc/haproxy/haproxy.cfg -c systemctl reload haproxy如果配置正确访问负载均衡的 80 端口请求会在两个业务节点之间轮流分发。6.3 故障注入脚本有了探活和自动摘除机制就可以做故障注入。这里通过 SSH 到测试节点模拟业务进程异常退出。#!/bin/bash # 文件路径scripts/fault-inject.sh # 作用在测试环境模拟业务节点异常退出 # 注意严禁在生产环境未授权执行 set -euo pipefail TARGET_IP$1 echo 开始注入故障节点 ${TARGET_IP} ssh root${TARGET_IP} systemctl stop app-node echo 故障注入完成 echo 观察 HAProxy 是否自动剔除故障节点 echo 1. 访问健康接口确认返回是否异常 echo 2. 查看 HAProxy 统计页面确认节点状态如果不想通过 SSH 远程执行也可以直接登录目标节点手动执行systemctl stop app-node。故障注入的方式不重要重要的是观察链路是否如设计般自动切换。这里特别强调权限边界所有故障注入命令必须限定在测试环境并且要有负责人审批。如果在生产环境做演练必须走变更管理流程并有开发者、运维、业务方共同确认。6.4 自动切换验证故障注入完成后持续访问负载均衡入口观察是否出现长时间报错。# 脚本路径scripts/access-loop.sh # 作用循环访问负载均衡入口记录响应码 URLhttp://192.168.1.10:80/ for i in $(seq 1 20); do code$(curl -s -o /dev/null -w %{http_code} --connect-timeout 5 ${URL} || echo 000) echo $(date %Y-%m-%d %H:%M:%S) code${code} sleep 1 done正常情况下故障注入后的短暂时间内可能有一两个探测请求超时但随后流量会全部落在健康的节点 B 上负载均衡入口会恢复 200。这验证了两个目标负载均衡的健康检查是有效的。故障节点不会被继续转发流量。7. 运行结果与效果验证以业务节点故障演练为例预期输出大致如下2025-01-10 14:00:01 code200 2025-01-10 14:00:02 code200 2025-01-10 14:00:03 code503 2025-01-10 14:00:04 code200 2025-01-10 14:00:05 code200 2025-01-10 14:00:06 code200这里需要注意“503”并不代表事故。它发生在健康检查判定节点不可用的过渡期属于预期窗口。实际生产系统中可以通过更精细的探活频率来缩小这个窗口比如把inter从 2 秒降到 1 秒配合更短的失败阈值。验证成功不能只看 HTTP 状态码还要关注以下指标切换耗时从故障注入到恢复正常的时间。请求失败率演练期间失败的请求占全部请求的比例。数据一致性如果涉及数据库切换要对比日志位点和数据内容。告警是否触发监控系统是否按预期发出告警。日志链路是否完整故障节点、负载均衡、健康节点的日志是否都有关键记录。如果切换后长时间仍然报错说明自动切换没生效。这时候第一步不是去重启业务进程而是按顺序检查健康状态先看健康检查接口是否返回 200再看 HAProxy 后端节点状态是否把故障节点标红最后看负载均衡的日志有没有拒绝连接。绝大多数切换失败都出在这三个环节的某一环。8. 常见问题与排查思路故障演练过程中会碰到各种奇怪的现象。下面整理几个高频问题问题现象可能原因排查方式解决方案故障注入后入口持续报错HAProxy 健康检查没有生效查看后端节点状态和探活日志确认httpchk配置正确目标接口可访问切换成功但请求仍然异常应用存在本地会话或缓存检查负载均衡是否开启了会话保持调整会话保持策略或引入分布式缓存数据库切换后数据丢失主备复制延迟过大对比备份位点和复制日志先修复复制再补数据最后再切换出现多主写入网络分区导致脑裂检查各节点的虚拟 IP 控制机制增加仲裁节点或采用更严格的故障判定故障恢复后流量没有切回节点重新加入条件未满足查看rise阈值配置配置更合理的重新上线阈值切换过程触发大量告警监控阈值和切换动作冲突统计故障注入时间窗口内的告警调整监控的持续时间条件避免误报这些问题的共性在于很多细节在正常状态下看不到只有真正注入故障后才暴露。这也是故障演练最大的价值所在。9. 工程最佳实践与团队建议9.1 渐进式演练不要一上来就“大宰鱼”第一次做故障演练不要直接选最复杂的全链路故障更不要在生产上做。建议先在测试环境跑通一个节点的假死场景再逐步扩展到数据库切换、网络分区、依赖服务中断。每一步都要有独立的观察指标。9.2 演练必须有完整剧本和回滚方案“开个会找个节点 kill 一下”不是演练是事故。标准的演练方案至少包含背景、场景、影响范围、操作步骤、回滚方案、负责人和观察人。回滚方案不是嘴上说说而是要在演练前实际验证过。9.3 把监控和告警放在演练目标里故障演练不只是验证自动切换也是在验证监控是否可靠。如果故障发生了告警没有触发那就说明监控体系有漏洞。每做完一次演练都应该同时修订监控规则和告警阈值。9.4 生产环境演练必须走审批和灰度在生产环境做故障演练要采用“灰度”思路。可以先选择一台低流量节点设定故障影响面并确保有快速回滚通道。整个过程要有负责人把关并且要和业务方提前对齐。9.5 最小权限原则必须贯穿到底所有演练脚本和自动化任务都不要使用万能账号。备份账号只做备份切换账号只做切换健康检查账号只做查询。这样做一方面降低误操作的风险另一方面也方便事后审计。9.6 吸收工具链的成熟成果如果团队具备一定基础可以直接引入混沌工程平台比如 Chaos Mesh、Litmus 或同类开源方案把场景定义、调度、爆炸半径控制、结果报告统一管理起来。工具不解决所有问题但能减少人为失误。10. 总结与后续学习方向99.999999999% 不是一个“设置项”更不是一个营销口号。它背后是每一层依赖都能被验证、每一个切换动作都可回滚、每一次故障都能被复盘的系统工程能力。“宰鱼”的过程本质上就是不断回答这几个问题系统里最可能出故障的“大鱼”是什么它一旦出问题我们的自动恢复机制是否真的能扛住每次演练之后架构和预案改进了什么真正让系统接近高可用目标的不是某一次成功的切换而是反复演练积累出的“可恢复性”。故障注入只是一个手段复盘和改进才是关键。如果你所在的团队还没做过完整的故障演练建议从这个月开始先在测试环境挑一个最让你担心的故障场景按这篇文章的流程跑一遍。把一个节点停掉看负载均衡会不会自动摘除把主库停掉看备库能不能顺利顶上去。你会发现在没有压力的环境里暴露出来的问题都是未来的救命稻草。
返回列表