给个人站做混沌工程实战:依赖故障注入、资源耗尽与回滚演练,用错误预算度量可靠性

为什么要给自己一个人的网站做「故障演练」

个人站长的日常是「出事再修」。数据库锁了、磁盘满了、证书过期了、CDN 挂了——每次都手忙脚乱地临时救火,修完写篇复盘就算完事。但有个更主动的做法:在系统正常的时候,主动制造小故障,验证你的监控、告警和应急方案是否真的管用。这就是混沌工程(Chaos Engineering)最朴素的应用形态。

混沌工程听起来很「大厂」,像 Netflix 的 Chaos Monkey 那样随机杀生产实例,个人站长当然玩不起也不该玩。但它的核心思想个人站完全可以借鉴:可靠性不是靠祈祷得来的,是靠反复演练验证出来的。你以为「重启一下就好了」的服务,真到重启时才发现配置文件有语法错误,服务起不来——这种问题在演练中暴露一次,就省下了真实故障时的几十分钟停机。

本文不谈复杂的平台,只讲个人站长用最少的成本,怎么给网站做三类实用的可靠性演练:依赖故障演练、资源耗尽演练、以及发布回滚演练。

先搞清「可靠性」的可量化指标:SLI 与 SLO

演练前得有个判断「什么算故障」的标准,否则演练完也不知道算不算失败。行业里通用的两个概念:

  • SLI(Service Level Indicator,服务等级指标):可测量的服务质量指标,比如「HTTP 200 响应的比例」「P95 响应时间」「每分钟成功处理的请求数」。它是原始数据。
  • SLO(Service Level Objective,服务等级目标):给 SLI 定的目标值,比如「99.5% 的请求在 500ms 内返回」「月度可用性不低于 99%」。它是承诺。

两者相减,就得到「错误预算(Error Budget)」:如果 SLO 是 99.5% 可用,那一个月里你可以有 0.5%(约 3.6 小时)的不可用额度。错误预算的意义在于:它把「要不要冒险做变更」变成一个算术问题。预算充足时可以大胆发版、做演练;预算快用完了就保守运维,冻结变更。

个人站不需要精确到小数点后几位,但至少应该给自己定个粗目标,比如「每月可用性不低于 99%,即宕机不超过 7 小时」。有了这个数字,演练时才有判断基准。

演练一:依赖故障注入——当数据库/缓存/上游 API 挂掉时,你的站会怎样?

你的网站几乎一定依赖一些外部组件:数据库、Redis、上游 API、CDN、DNS。这些依赖一旦不可用,你的服务是「优雅降级」还是「整站 500」?这个答案必须通过演练来验证,而不是靠猜。

演练方法(低风险版):用防火墙规则临时阻断某个依赖的连接,观察站点表现。比如临时封掉到 Redis 的连接,看页面是返回缓存兜底内容,还是直接报错:

# 临时阻断到 Redis (6379) 的出站连接
iptables -A OUTPUT -p tcp --dport 6379 -j DROP
# 观察 1-2 分钟,用浏览器和监控看站点表现
curl -s -o /dev/null -w "%{http_code} %{time_total}" https://你的域名/; echo
# 演练结束,立刻删除规则恢复
iptables -D OUTPUT -p tcp --dport 6379 -j DROP

关键提醒:演练必须在你有空盯着的时候做,且准备好「一键恢复」的命令。上面的 iptables -D 就是恢复命令,演练前先在浏览器里准备好,一旦发现站点真的挂了且降级逻辑没生效,立刻恢复。千万别在半夜或出门前做这类演练。

预期结论:通常会发现两类问题。一是根本没有降级逻辑——Redis 一挂直接 500,那你就该给关键读取路径加「缓存不可用时直连数据库」的兜底;二是降级逻辑写了但有 bug(比如超时设太长,导致请求全部卡住等待,比直接失败还慢)。这类「等待型雪崩」是最隐蔽的问题,演练能暴露它。

演练二:资源耗尽演练——磁盘、内存、连接数打满会怎样?

个人站最常见的真实故障是资源耗尽:磁盘写满、内存耗尽触发 OOM Killer、连接数打满。这些都可以在受控条件下提前演练。

磁盘写满演练

用一个临时大文件把磁盘占用推到接近 100%,看你依赖磁盘的组件(日志、数据库、上传)会怎样报错,以及你的告警是否触发:

# 生成一个填充文件占满剩余空间(注意先用 df 算好大小,留 100MB 余量)
fallocate -l 5G /tmp/fill_test.img
df -h /   # 观察使用率
# 观察站点、数据库、日志写入的表现
# 演练结束立即删除
rm -f /tmp/fill_test.img

你会观察到的典型现象:数据库写入报错「No space left on device」、日志停止滚动、上传接口 500。更重要的是检查你的磁盘告警有没有在 80%/90% 时预警——如果直到 100% 才发现,说明告警阈值设得太晚。

内存压力演练

用 stress-ng 施加可控的内存压力,观察你的服务是否被 OOM Killer 杀掉、有没有被保护:

# 分配 1G 内存并保持 60 秒
stress-ng --vm 1 --vm-bytes 1G --timeout 60s
# 期间另开终端观察内存与是否有进程被杀
dmesg -T | tail -20   # 看有没有 OOM Killer 记录

如果发现关键进程(数据库、Web 服务)被 OOM Killer 杀掉,就该给它设置保护——通过 oom_score_adj 降低被杀的优先级,或在 cgroup 层面给它预留内存。

连接数打满演练

用压测工具制造大量并发连接,观察数据库连接池、Nginx worker 连接数、系统的 nofile 限制是否成为瓶颈:

# 简单压测,制造 200 并发持续 30 秒
ab -n 10000 -c 200 https://你的域名/

这类演练的价值在于找出「第一个被打满的资源」。往往是某个不起眼的限制(比如 PHP-FPM 的 pm.max_children、MySQL 的 max_connections、系统 ulimit -n)先到顶,然后引发连锁反应。找到它,你就知道该给哪里扩容。

演练三:发布与回滚演练——你敢不敢「随时回滚」?

可靠性里最被低估的一环是「回滚能力」。很多人只想「怎么把新版发上去」,从不想「发砸了怎么快速退回去」。真出事时,手忙脚乱地改代码、重新打包、重新部署,停机时间成倍增长。

演练内容很简单:发布一个新版本,然后立刻演练一次完整回滚,计时。目标是把回滚时间压缩到可接受范围(比如 5 分钟内)。具体验证这几件事:

  • 回滚是「改一个指针/符号链接」还是「重新构建部署」?前者秒级,后者可能十几分钟。
  • 回滚命令是否已经写好在文档里、粘贴就能用?还是需要现场回忆?
  • 数据库变更能否回滚?如果新版本改了表结构,旧版本代码还能跑吗?这是最容易翻车的地方——很多「回滚」在代码层成功了,但数据库结构已经不兼容,一回去就崩。

一个稳妥的做法是:数据库变更永远向前兼容。比如加字段而不是改字段类型,新旧版本代码都能跑;先上代码兼容新结构,确认稳定后再清理老字段。这样回滚代码时数据库不用动。

演练的纪律:别把演练变成事故

混沌工程最容易犯的错就是「玩脱了」——本想在测试环境演练,手一抖在生产执行了;或者演练的恢复命令写错,故障没恢复。三条纪律必须遵守:

  • 先在非关键环境演练:如果有测试环境就先去测试环境;没有的话,选一个访问量最低的时间段(比如凌晨),并且保证你全程在线。
  • 每次演练都有明确的「终止条件」和「恢复步骤」:演练前把恢复命令写在终端里(不执行),发现不对立刻回车恢复。
  • 演练要有记录和结论:记下「注入了什么、观察到什么、发现了什么薄弱点、要不要修」。没有结论的演练等于白做。

顺序上建议由轻到重:先做「只读型」演练(观察,不注入故障),再做「可秒级恢复」的注入(如上面封端口的例子),最后才考虑需要重启服务的演练。永远从影响面最小、恢复最快的那一类开始。

演练之后:把发现固化成改进项

演练的产出不是「演练本身」,而是它暴露的问题清单。每次演练后,理想情况下你至少能得到一两条具体的改进项,比如:

  • 给 Redis 挂掉时加降级逻辑,避免整站 500。
  • 把磁盘告警阈值从 95% 调到 85%,提前预警。
  • 给数据库进程设置 OOM 保护,避免被误杀。
  • 把回滚流程写成脚本,把回滚时间从 20 分钟压到 2 分钟。

把这些改进项排期做掉,你的系统可靠性就实质性地提升了一档。下一轮演练时,再验证这些改进是否真的生效——演练 → 改进 → 再演练,这个闭环才是混沌工程对个人站长真正的价值所在。

总结

混沌工程对个人站长不是什么高不可攀的大厂玩具,它的内核就是「在系统正常时主动制造小故障,验证监控和应急是否管用」。落地三步:给自己的站定一个粗粒度的 SLO(比如月可用性 99%)作为判断基准;用最小成本的注入手段(封端口、填磁盘、打并发)分别演练依赖故障、资源耗尽和发布回滚三类场景;每次演练后把暴露的薄弱点固化成具体的改进项,并在一轮演练中验证。三条纪律贯穿始终:先在低风险环境/时段做、每次都有明确的恢复命令、演练必须有记录有结论。做到这些,你的网站就从「靠运气不出事」进化到「知道哪里会出事、出事多久能恢复」——这才是可靠性真正的底气。

Last modification:October 14th, 2026 at 09:30 pm

Leave a Comment