太长不读版
- 即使每一个微服务都在测试部门的QA环境上通过了测试,当将其一个个部署到生产环境后,也必然会发生故障。
- 不能以通过测试部门的QA测试为目标,而应该为生产环境的韧性而设计。
- 当使用鱼骨根因分析法、因果回路图或者麦肯锡MECE分析法进行分析时,要小心不要用错场景,掉进复杂混沌世界的深渊。
- 面对复杂混沌的生产环境,应该本着“不信有好事”的原则来进行“明哲保身”式的软件设计,并在生产环境中利用混沌猴以及在办公区进行僵尸来袭模拟的混沌工程实践,来持续地主动揭示系统弱点,并加以改进,以提高系统韧性。
“阿里云挂了!”
5个月前的一个下午,正在客户现场的我,从微信群里得知了这一消息。
阿里云在那天,至少挂半小时。“我们在运维上的一个操作失误,导致一些客户访问阿里云官网控制台和使用部分产品功能出现问题,引发了大量吐槽。”
这是典型的一次黑天鹅事件,符合黑天鹅的三个特性:
- 完全出乎意外;
- 带来极端影响;
- 事后可编故事。
“对于这次故障,没有借口,我们不能也不该出现这样的失误!”
阿里云的上述表态充满了自责,虽然令我能感受到其诚意,但更好的表述或许是:对于这次故障,我们一方面表示歉意,另一方面要改进系统设计和验证过程,使得今后局部的故障不会导致全局的失效。
对于阿里云这样复杂的系统,挂了的风险不可避免。
对于任何复杂的系统,失误必然会发生,只是敬畏还不够。因为——
复杂和混沌的系统无法预测
科幻作家刘慈欣所创作的《三体》,受到了下面三体问题的启发。
“对于3个任意质量、初始位置和速度的天体,只在牛顿万有引力定律的作用下运动,那么能预测这3个天体的运动情况吗?”
在130多年前,研究过这个问题的德国数学家布隆斯和法国数学家庞加莱得出这样的结论:对于三体问题,不存在能用简单代数和积分表达的一般解析解;除特殊情况外,三体的运动通常不会重复。
面对这个问题,庞加莱运用了他发明的相图理论,并且最终发现了混沌理论。
无法找到解和不会重复,就无法预测。
在三体问题中,科学家们熟悉其中每一个天体的性质,熟悉万有引力。
但当3个这样的天体在一起互动时,运动的轨迹却不可预测。
复杂的生产环境,尤其是微服务系统的生产环境,至少有3个各自独立通过了测试的系统,它们各自通过事先定义好的API实现通信。
但当它们在一起互动时,结果同样不可预知。
虽然庞加莱发现了混沌理论,但从那之后的70多年间,机械论的世界观仍然在科学界占据主导地位。
机械论认为,大自然就像一台精密的机器,只要我们发现了其中的函数,就能精确预测事物的发展,就像精确预测日食、月食、潮汐那样。
如果再凭借计算机运算这些函数,就能精确模拟原子弹爆炸和弹道轨迹,甚至能描述天气的演化过程。
55年前的一天,美国气象学家洛伦兹正在第二次用计算机计算一段时间的气候变化。不过这一天,他没让计算机从头计算,而是特地选择从昨天计算的一个中间结果0.506开始往后计算。
为了更细致地考察结果,他把这个中间结果的精度提高到0.506127,然后就让计算机使用与昨天同样的函数开始计算。
因为使用的是同样的函数,即使精度提高了一点,后半段天气演化的过程也应该和昨天的结果相吻合。他一边想,一边去咖啡馆喝了杯咖啡。
等他回来查看时,顿时大吃一惊:与昨天的天气演化曲线相比,刚刚引入的0.000127这么小的差异,随着函数中时间变量的推移,从重合开始逐渐分道扬镳,最终令计算出的最后结果发生了天壤之别。
再次验算,他发现计算机的计算没有问题。
他由此发现,刚刚引入的那个微小误差,会以非线性的方式增长,从而造成截然不同的后果。
更重要的是,这种微小的误差很容易被人们所忽视,此时又怎样能找出造成不同后果的原因呢?
这便产生了蝴蝶效应这个惊世说法:一只南美洲亚马逊热带雨林中的蝴蝶,偶尔扇动了几下翅膀,就可以在两周后引起美国得克萨斯州的一场龙卷风。
如果把蝴蝶换成为生产环境编写软件的工程师呢?
一年多前的一天,一位亚马逊S3服务团队成员正在根据运维剧本移除少量的服务器。一不小心,他输错了命令,导致多于预期的服务器被移除。此后的4个小时内,美国成千上万的网站无法正常运转。
在复杂和混沌的系统中,人、事、物之间的相互作用大多都是非线性的,初始条件千差万别。
**三体互动无法预测,细微原因不易发现。**此时,又如何能保证“不能也不该出现失误”呢?难道要——
用解决“石头、剪子、布”的简单思路,来解决气象预测之类的混沌问题?
英国咨询师斯诺登用他所创造的“栖息地”框架,把世间的问题分为5种:
- 明显的问题
- 繁杂的问题
- 复杂的问题
- 混沌的问题
- 紊乱的问题
明显的问题,其中的约束少而线性,就如同两人玩“石头、剪子、布”那样。
繁杂的问题,其中的约束虽然多些,但也都是线性的,就如同修汽车那样。
复杂的问题,其中的约束会是非线形的,且经常发生变化,有时会自相矛盾,就如同太太们打麻将那样。
混沌的问题,其中的约束难以捉摸,就如同气象或三体问题那样。
在明显的问题和混沌的问题之间有一个陡峭的悬崖,这意味着如果盲目地用解决“石头、剪子、布”的简单思路,来解决气象预测之类的混沌问题,那么就会坠入深渊。
阿里云和亚马逊AWS服务属于哪类问题呢?
阿里云和AWS中的每一个微服务,可以看作是明显或繁杂的问题。但我们能在一个相对简单的QA测试环境来验证每一个微服务及其API,然后将它们部署到生产环境,并声称它们之间能按预期运行吗?显然我们不会那么自信,因为此时隐约看到了三体问题混沌的影子。
我们能用“相互独立和完全穷尽”的麦肯锡MECE分析法,来分析阿里云事故吗?我们能完全穷尽所有的原因吗?
“这一功能在测试环境验证中并未发生问题,上线到自动化运维系统后,触发了一个未知代码bug。错误代码禁用了部分内部IP,导致部分产品访问链路不通。”
在上面阿里云故障官方说明中,当看到“未知代码bug”,就知道这是一个“不可知的未知”的混沌问题。此时还何谈“完全穷尽”?
即使找到了那段有bug的代码,定位到了写代码的程序员,程序员能否完全穷尽各种原因?人家甚至都不愿意说出原因。
在这种混沌情况下,鱼骨根因分析法和因果回路图,都统统黯然失色。因为除了原因无法穷尽,因果回路图还会给人一种原因已被穷尽的自我陶醉的假象。
在复杂和混沌的系统中,我们要——
专注于持续发现约束,而不是寻找根本原因
应对黑天鹅事件的策略可以有3个:
- 承认无法预测黑天鹅;
- 增强个体抗打击能力;
- 设法利用试错来获利。
其中,承认无法预测黑天鹅是关键。否则,又会坠入在混沌问题中寻找根因的悬崖。
约束能启发我们识别临界状态,促使我们把各个微服务打造成“明哲保身”且能自愈的自治子系统,增强个体抗打击能力,从而把整个系统打造成一个韧性系统。
系统韧性(Resilience),是系统在变化和干扰的发生之前、发生期间或发生之后,调整其功能的内在能力,从而可以在预期情况和意外情况下,依然能做出所需的操作。由于系统韧性是指能够让系统发挥作用,而不是去抵御失效所带来的影响,因此在生产力和安全性之间不存在冲突。
面对复杂和混沌的整个微服务系统,要验证其韧性,不可能构建第二个阿里云去做测试,也不能凭借每个微服务自己的QA环境来验证,那么怎么才能构建韧性系统来避免灾难呢?
20多年前的一天,美国理论物理学博士生马克・布查纳正在用一个沙堆来可视化复杂系统中的“灾难”。
他发现,当沙子一粒粒落到平面上,逐渐堆积成一个小沙丘后,处于陡峭位置的沙粒会越来越多,全都变得“命悬一线”,从而形成了一个“灾难”前的临界状态。
此时当有沙子继续落下,就会令处于临界状态的沙丘变得非常敏感,任何一粒沙子都有可能引发沙丘的大规模崩塌。
一个复杂系统,达到临界状态,再来一点触发,随时灾难爆发。
但是,沙丘一旦崩塌,则又恢复为比较平坦的相对安全的状态。
据说,美国林业部门在预防森林火灾时,已经采取偶尔主动引发中小规模的火灾,来清除树木不断堆积的易燃物,避免森林进入火灾临界状态,从而预防大型火灾。
在平时人际交往中,尽量及时沟通,避免积累情绪和敌意,也是避免进入临界状态的一种方法。
我们自然也可以利用上述避免临界状态的思路,在生产环境中主动进行一些混沌工程的试验,比如关闭一些“非致命”的服务器实例。一方面验证系统是否能自愈,另一方面也是提前多次小范围“引爆”,以释放“能量”,以免系统达到发生严重灾难的临界状态。这也是设法利用试错来获利的一种实践。
针对复杂和混沌的微服务生产环境,我们可以使用下面的方法来设计系统韧性。
-
识别问题种类。使用“栖息地”框架来确定生产环境属于哪种问题。如果判断生产环境是复杂和混沌的问题,那么在分析问题时,就不要使用麦肯锡MECE分析法、鱼骨根因分析法和因果回路图,承认无法预测黑天鹅,转而使用下面的方法。
-
确定系统愿景。 栩栩如生地描述系统在所期望的状态下是如何运行的。系统未来引人入胜的愿景,确定了下面行动的方向。比如:即使错误代码要禁用部分内部IP,系统仍然能保证核心产品访问链路依旧通畅。
-
系统成员关系。画微服务系统用例图,在图中画出微服务系统所有的核心服务、支持服务和通用服务,及其相互关系,并画出与这些服务交互的人和外部系统。
-
识别系统约束。参考用例图,对用户旅程进行事件风暴,并在其中识别约束、人和外部系统。在识别约束时,一方面对于与系统交互的人,要考虑“他们的感受和自尊心”,因为这些也是我们要考虑的约束;另一方面,也要考虑约束的延迟性,警惕忽视“时滞性”的不耐烦所导致的系统“振荡”,比如总也调不好水温的淋浴器。
-
明哲保身原则。抱着“不信有好事”的信念对每个微服务进行设计和重构,以增强个体抗打击能力。每一个微服务经过这番设计后,即使其所依赖的其他微服务都发生故障,甚至自身也发生了局部故障,也能继续运行。此时可以针对上面所识别出的约束,考虑使用超时、断路器、舱壁、快速失败、任其崩溃并替换等设计模式来增强系统韧性。同时要避免连累反应、层叠失效、阻塞的线程、自黑式攻击、叠罗汉、缓慢的响应、无限长结果集等反模式。
-
频繁小型“引爆”。既然不可能构建第二个阿里云去做测试,那我们可以考虑使用混沌猴工具,来在生产环境中主动关闭一些非“致命”的服务实例,以检验微服务系统是否能自动修复故障,另外更重要的是能频繁小批量地释放“能量”,以免系统达到灾难前的临界状态。此时一旦发现弱点,就立即进行改进。为了验证团队在人员发生变动的情况下依旧能正常运转,可以考虑进行“僵尸来袭模拟”,即从团队里随机选择50%的人,并告诉他们在当天会被视为僵尸,他们虽然来上班,但需要远离工作,并且对与其所进行的任何沟通尝试都不做任何反应。此时同样要识别在团队组织方面所发现的弱点,并进行改进。这样不断小规模试错,就能获得系统稳定性的最大利益。
总结
- 三体互动无法预测,细微原因不易发现,复杂和混沌的系统无法预测。
- 面对复杂混沌系统,用解决“石头、剪子、布”的简单思路,会坠入深渊。此时应该专注于持续发现约束,而不是寻找根本原因。
- 约束能启发我们识别临界状态,促使我们把各个微服务打造成“明哲保身”且能自愈的自治子系统,并用频繁小型的引爆避免进入临界状态,从而把整个系统打造成一个韧性系统。
文/ThoughtWorks伍斌
更多精彩洞见,请关注微信公众号:ThoughtWorks洞见