基于网络的分布系统有天然的复杂性。它们由许多可移动的组件组成,Web 服务器、数据库、负载均衡、内容分发网络,以及等等,它们作为一个复杂的整体一起协作。这种复杂度不可避免会导致失效的可能。理解这种失效是如何发生的(以及如何预防它)是我们作为运维工程师的核心职责。
Richard Cook 在他很有影响力的一篇论文《复杂系统是如何失效的》中,分享了18 个就复杂医学系统中失效的性质的敏锐观察。最棒的是,这些观察大多数适用于通常的复杂系统。我们的因果直觉会认为每次运行中断都能找到直接原因,而这并不适用于实际的现代系统。
在这篇文章中,我将把Cook 的观察转变为适合于我们心爱的Web 系统的内容,探索它们是如何失效的、为什么会失效、可以针对运行中断做怎样的准备,以及如何预防未来发生类似的失效。
失效总是随侍在侧
或早或晚,任何复杂系统都有可能失效,Web 系统也不能例外。失效随时随地都有可能发生。所以你一刻也不能掉以轻心。
Web 系统这样的复杂度难免会有多种缺陷,潜在的 bug 会在特定时刻冒出来。我们不会也不能把它们全都修复掉,因为这么做不经济,而且难以想象个体失效可能会为重大事故带来什么“贡献”。我们倾向把这些个体缺陷作为次要因素来考虑,但看似次要的因素聚在一起却可能导致大灾难。
在 2012 年十月,AWS 在美国东部遭受了一次严重的运行中断,部分原因是 EBS 服务器数据收集代理中潜在的内存泄露。该泄漏看起来是个次要的问题,但两个次要问题(单数据收集服务器的例行替换,以及内部 DNS 更新后重定向的通信远离了替换后的服务器)“联手”导致整个地区宕机数小时。
复杂系统默认情况下作为受损的系统运行。得益于数据库副本、服务自动扩展等几个可靠性指标,大多数时间它们都可以持续工作。当然,这也得感谢监控和预警,以及运维工程师的常识,这能让他们在问题出现时就进行修复。但是,系统仍会在某一时刻失效的,这是不可回避的事实。
在事故发生之后,事后分析可能会发现仿佛是昨日重现,这个事故似曾相识,好像之前就出现过,运维工程师应该在出现之前就发现呀。然而,这个看法太简单了。系统运维是动态的:人员可能被更替,失效的组件可能被更替,资源的使用可能会发生变化。
亚马逊在 2012 年运行中断事后分析中指出,EBS 服务器通常对内存的使用是很动态的,所以数据收集代理中的内存泄漏与普通的波动没什么特别的不同。内存泄漏和检查类似泄漏的能力是在事故之前确定下来的,这将诱使把根本原因指向它们。然而,归因并非这么简单,随后你就会看到。
根本原因的神话
在复杂的 Web 系统中,就没有根本原因。单一的失效不足以诱发事故。相反,事故需要多个“帮凶”,每个都需要,而且是充分必要条件。它是这些原因的组合,通常它们是小而无妨的失效,比如内存泄漏、服务器更换和错误的 DNS 更新(它是事故的先决条件)。所以我们不能割裂来看这些原因。我们喜欢去寻找结果单一的、简单的原因,这是因为这个失效对于我们的大脑来说太复杂了。所以我们会简单化地处理,在并未真正理解这个失效的本质的情况下,把一个原因作为根本原因抓住不放。
事后诸葛亮一直都是失效研究的主要障碍。这种认知的偏见,也称为“百事通”效应,说的是人们尽管缺乏客观证据却仍喜欢高估自己预测的能力。相反,事后诸葛亮导致无法在事故之后准确评估人的绩效。至今,仍有许多公司在他们应该去真正怪罪并修复他们并不完善的流程时去责备犯了错的人。
同样,公司还喜欢限制在未来会导致类似事故的活动。这种企图纠正“人的错误”的做法不禁令我想起了机场安检区,各地旅客把洗漱用品放到小框子里,赤脚步行通过身体扫描,尽管没多少证据表明这实际上能预防安全事故。
未深思熟虑的缓解措施能造成更多的浪费,它们实际上会使系统更为复杂、脆弱、不透明。例如,亚马逊本来也可以通过开始一个规则去回应 2012 年的运行中断,该规则即所有内部 DNS 缓存必须在替换数据收集服务器之后刷新。这能预防一模一样的事故,但也会引发系统以新的方式导致失效。
结果论与之类似,却也有不同,它指的是喜欢通过最终结果来评判决策。我们不得不记住每种结果,成功与否,就像在赌博。我们认为我们清楚所有的事(换句话说,因为我们在以如此这般的方式在构建系统),但有些事我们并不清楚,甚至有些事我们不清楚我们还不清楚。我们 Web 系统的复杂度总是未知的。我们不能排除不确定性,比如什么可能会出错以及什么可能会修复它。
人类运维的职责
不管工具化和自动化的程度如何,都离不开人类的运维,他们保障着 Web 系统的正常运行。他们确保这些系统保持在可接收的性能范围内,无故障的运行离不开他们的努力。大多数工作有很清楚的处理过程(在操作手册里已经写得很清楚了),比如恢复错误的部署。然而,有时修复一个出了问题的系统就需要一个新奇的方法了。在不可逆的故障中尤其需要后者,在这种情况下你不能简单地撤消导致问题的操作。
当亚马逊在 2015 年 9 月经历一次 DynamoDB 服务中断时,最初引发事故的事件(网络中断)被快速修复了。然而,系统随后陷入整体服务超载的状态,亚马逊运维工程师只好连忙写了一个长长的处理过程,引导系统回到稳定状态。
同样的运维渐进改进系统,调整它们去适应新的情况,以便它们能持续满足生产的需要。这些调整包括如:
- 系统组件解耦,从而减少易受伤害的组件暴露在外导致失效的可能。
- 容量规划,从而将资源集中在预料会有高需求的区域。
- 优雅的错误处理和定期的备份,从而能够从预期和非预期的故障中恢复。
- 设立变更系统性能的早期检测方式。
运维工程师有两个职责:构建、维护和改进系统;以及对失效作反应。这种双重身份并没有被一直承认。它的重点在于维持平衡。如果“构建”的职责太大,就没时间对失效作出反应,并会诱使其忽略掉初期的问题。如果失效反应占了太多的时间,那么系统将会失去维护而逐渐出现问题。
变更引入新的失效方式
甚至对系统的变更已经深思熟虑过,也会经常得到计划外的负面结果。因为有着高频率的变更和时常导致这些变更的这样那样的处理。在这种情况下,你很难充分理解所有模块在各种条件下的相互影响。这正是为什么运行中断即无法避免又无法预测的主要原因。
由于技术进步、工作组织演变以及为消除失效的自相矛盾的做法,运维工程师不得不应对因此而产生的千变万化的失效。在可靠的系统里,严重事故的频率较低,这可能会鼓励大家努力去消除低严重程度而高频率的失效。但这些变更可能实际会导致更多新的、低频率但严重程度高的失效。因为这些失效的发生频率低,就难以发现应由哪些变更为它们“负责”。
谷歌的 2016 年 4 月 GCE 运行中断报告中提供了一个例子,工程师刻意执行了一个清除从未用过的 IP 限制的操作,这引发了潜藏在自动化配置管理系统中的 bug。这个 bug 在该系统中引起了安全故障,它把所有 IP 阻塞都从网络配置中清除了,只过了 18 分钟,就使整个 GCE 平台离线了。我们特别使用了自动化去避免会导致网络故障的错误,然而现在,我们却看到它同样带来了意想不到的后果。
在第一线上的行动
往往,公司并不清楚可接受事故的相关政策。在缺乏硬性数字的情况下,决策经常是根据某个人的直觉做出来的。
这种不确定性是在系统第一线采取行动来解决的。有些事在灾难对生产造成影响之后,我们才会了解,例如:
- 管理者对失效的反应
- 什么是顺利的,什么出错了
- 我们是否需要雇用其他网站可靠性工程师
- 我们是否应该在员工培训或更好的设备上进行投入
换句话说,我们是被迫去思考和决策的。
再次强调一下,我们需要警惕事后诸葛亮和类似的做法,永不忽略其他驱动因子,尤其是事故发生后生产的压力。
失效的经历至关重要
当失效是例外而非常态时,我们就要冒麻痹大意的风险了。麻痹大意是还原能力的敌人。当我们蓄势待发准备应对灾难等待灾难时,等得时间越长就越喜欢去处理那些紧急的事,会简单地寄希望每件事都会正常的,不论技术和组织层均是如此。构建可还原的系统需要失效的经历。事后从事故中总结经验非常重要,但不应该仅仅成为获取运维知识的方式。
不能等着什么事被破坏。我们应该通过在第一线做些什么去提前触发失效,为最坏的情况做好准备,并增加对我们系统的信心。这是混沌工程及其相关实践(比如 GameDay 练习)的核心思想。如果你想到了解混沌工程的更多内容,请访问以下链接:
结论
复杂系统本质上就是存在危险的系统。虽然很幸运大多数 Web 系统不会将我们的生命置于危险的境地,但失效仍将产生严重的后果。因此,我们要采取适当的对策,备份系统、监控、DDoS 攻击防护、作业指南、GameDay 练习,等等。这些措施意在提供一系列层层防护。大多数失效的轨迹是通过这些防护或系统运维工程师亲自给阻断的。
即使具备适当的防护,复杂的 Web 系统仍会遭到破坏。但是,我们并非没有机会。通过承认失效的必然性,避免简单的事后分析,学着去拥抱失效,以及在本文中描述的其他措施,你可以不断降低失效的频率和严重程度。
特别提醒:_Scalyr_ 的日志管理服务把强大的日志分析与灵活的仪表板和警报系统绑在了一起。它很简单就能快速上手,鼓励那种深入的研究和前瞻性的监控,这能帮你让系统保持在有利的一面。
Mathias Lafeldt 对网站可靠性工程、混沌工程和系统化思维有强烈的兴趣。他经常就这些主题发表很有思想性的文章。可通过 Twitter 与他取得联系。
评论