[译] 一切系统都是分布式的(OReilly, 2015)
译者序
本文内容来自 2015 年的一本小册子 Everything is distributed(下载 Free-OReilly-Books), 其中集合了 5篇与性能和运维相关的文章,本文翻译其中第二篇 Everything is distributed。
这篇文章思考有一定深度,但部分观点恐怕失之颇偏,比如作者认为分布式系统中的故障没 有根本原因(There is no root cause)、查找 root cause 多半是徒劳等等。
本文内容仅供学习交流,如有侵权立即删除。
由于译者水平有限,本文不免存在遗漏或错误之处。如有疑问,请查阅原文。
目录
以下是译文。
物流中心(图片来源)
人们应该感到惊讶的并不是每天都有这么多故障,而是每天只有这么少故障。你不应该惊 讶于自己的系统偶尔会崩溃,而应该惊讶于它竟然能长时间不出错地运行。
— Richard Cook
2007 年 9 月,76 岁的 Jean Bookout 正在 Oklahoma 一条陌生的道路上驾驶着她的丰田 凯美瑞,她的朋友 Barbara Schwarz 坐在副驾驶的位置。突然,这辆汽车自己开始加速。 Bookout 尝试了踩刹车、拉手刹,但都不管用,汽车还是继续加速。最后这辆车撞上了路堤 ,造成 Bookout 受伤,Schwarz 死亡。在随后的法律程序中,丰田的律师将事故原因指向 此类事故最常见的罪魁祸首:人为失误(human error)。“人们有时会在开车时犯错” ,其中一位律师宣称。Bookout 年纪很大了,而且也不是她熟悉的路,因此造成了这场悲剧 。
然而,近期一个针对丰田的 产品可靠性测试 却令这件事情有了一个 180 度的大转弯:凯美瑞中的一个软件 bug 导致的栈溢出错误( stack overflow error)才是此次事故的罪魁祸首。下面两方面原因使得这一事件非常重要:
- 此类事故最常见的背锅侠 —— 人为失误 —— 最后确认并不是造成这次事故的原因( 这个假设本身就是有问题的)
- 这件事展示了我们如何从 一个软件错误导致的小故障或(潜在更大的) 公司营收损失,无缝跨越到了 人身安全 的领域
要将这件事情往小里说可能也容易:(目前)在某款特定车型搭载的软件中似乎发现了一个常见 bug 。
但这件事的外延要 有趣地多。考虑一下目前发展地如火如荼的自动驾驶汽车。自动驾驶消除了人为失误 这个背锅侠,那我们得到的结论将是:在很多方面,自动驾驶汽车要比传统汽车更加安全。 但事实真是这样吗?考虑下面的情况:
- 如果发生了完全在汽车自动驾驶系统控制之外的事将会怎样?
- 如果训练汽车识别红绿灯的数据有错误怎么办?
- 如果 Google 地图让它去做一些明显很愚蠢的事,并且这些事很危险怎么办?
我们已经到达了软件开发中的一个特殊点 —— 不管是在技术上还是在社会/组织上,到 了这个点我们不再能理解、看到、或控制系统的所有组件 —— 我们的软件正在变得越来越复 杂和分布式。软件行业本身已经变成一个分布式的、复杂的系统。
我们如何开发和管理那些庞大到无法理解、复杂到无法控制、出错方式也无法预测的系统?
1. 拥抱故障
分布式系统曾经只是计算机科学博士和软件架构师的领地,受众非常小。但现在不同了。 仅仅因为你在笔记本电脑上写程序、无需关心消息如何传递和锁问题,并不意味着你不 需要关心分布式系统:
- 你写的程序发起了多少对外部服务的 API 调用?
- 你的代码是跑在PC 上还是移动设备上 —— 你确切地知道所有可能的设备类型吗?
- 当你的应用正在运行时,它可能遇到哪些网络方面的限制,关于这些你知道多少?
- 当软件到达特定规模时,它会遇到哪些瓶颈,关于这些你又知道多少?
在经典分布式计算理论中,我们学到的一件事情是:分布式系统经常会发生故障,而且 大都是局部而非全局故障。这些故障不仅难于诊断和预测,而且很难复现 —— 可 能是某个特定的第三方数据流没数据了,可能是位于某个你从未听说过的地方的路由器挂掉 了。你永远在同短时故障(intermittent failure)作斗争,这注定是一场失败的战役 吗?
应对复杂分布式系统的方法并不是简单地增加测试,或者采用敏捷开发流程,也不是采用 DevOps 或者持续交付(continuous delivery)。任何单一的技术或方法都无法阻止类似 丰田汽车事故这样的事情再次发生。实际上,类似这样的事情肯定会再次发生。
解决这类问题我们需要拥抱这样一种观念:无法预知的故障种类太多了 —— 我们面对的是一 片巨大而未知的未知海洋;此外,还需要改变我们构建系统时 —— 以及运维现有系统时 —— 的思考方式。
2. 分布式设计,本地化开发
好了,现在我们可以确定的一点是:每个编写或开发软件的人都需要像分布式系统工程师 一样去思考。但这句话到底意味着什么?在实际中,它意味着:丢弃那种单计算机(节 点)的思考模式(single-computer mode of thinking)。
直到最近,我们才可以将计算机视为一个相对确定性的东西(a relatively deterministic thing)。当编写一个在某台机器上运行的代码时,我们能够确定性地假设很多东西,例如 ,内存查询的方式。但现在已经没有应用还运行在单台机器上了 —— 云就是这个时代的计 算机(the cloud is the computer now),它就像一个生命系统(living system),一 直在持续不断地变化,尤其是在越来越多的公司开始采用持续交付这种新范式的过程中。
因此,你必须开始:
- 接受这样的假设:支撑你的软件运行的系统一定会发生故障
- 对为什么会发生故障以及故障可能会以怎样的形式发生做出预案
- 针对这些预案设计数据收集方案
这并不是像说一句“我们需要更多测试”那么简单。传统的测试哲学中,假定 所有测试用例都是能够描述出来的,但在分布式系统中这一点不再成立。(这并不是说 测试不重要了,而是说测试不再是万灵药。)
当处于一个分布式环境、并且大部分故障模 式都是无法提前预测也无法测试时,监控就成了唯一的理解应用行为的方式。
3. 数据是分布式系统的通用语言
如果对刚才的比喻(复杂系统就像一个生命系统)进行延伸,那在 诊断出一个人中风后 才去寻找病因 与 在中风前就能及早发现问题 明显是两种方式。你当然可以翻阅病 例上的就诊记录,从中看出其实早有中风的苗头,但你更需要的是一个早期告警系统, 以及一种在问题刚发生时就能看到并尽可能快地介入处理的方式。
另外, 历史数据只能告诉你哪里出了问题,并且是局限在特定时间段内的问题。但在处理分布 式系统相关的问题时,需要关心的事情要比仅仅 ping 一下服务器通不通多多了。
与测量和监控相关的工具现在已经有很多,这里不会就具体工具展开讨论,而是要告诉你: 在查看自己的应用和系统的监控数据的过程中,你会对“直方图通常比平均值更能说明问 题”有越来越深的理解,在这个过程中开发者不会再将监控视为纯粹是系统管理员的领 域。
4. 复杂系统中人的角色
无论多么复杂的软件最终都是人写出来的。
任何对分布式系统和复杂度管理的讨论最终都必须承认 人在我们设计和运行的系统中 的角色。人是我们创造出来的复杂系统中不可分割的一部分,而且很大程度上我们要对他 们的多样性(variability )和适应性(resilience )负责(或对他们缺乏这两种特性负 责)。
作为复杂系统的设计者、建造者和运营者,我们受一种厌恶风险(risk-averse)文化 的影响,不管我们是否意识到这一点。在试图(在进程、产品或大型系统中)避免故障的过 程中,为了使自己能够有更多“把控”(control),我们倾向于粗细不分地列出需求( exhaustive requirements)和创建紧耦合(tight couplings),但这种方式经常 更容易导致故障,或者产生更脆弱的系统。
当系统发生故障时,我们的方式是责备(blame)。我们粗鲁地寻找所谓的故障“原因” —— 实际上,相比于寻找真正原因以避免将来再出现类似问题,这种所谓的寻找故障“原因”的 过程经常只是一个减轻负罪感和寻求内心平静的活动。这类活动通常会导致人们继续加强对 系统的“把控”,而结果是最终的系统更加脆弱。
这里的现实是:大部分大故障都是一连串小故障叠加的结果,最终触发了某个事件(most large failures are the result of a string of micro-failures leading up to the final event)。这些故障并没有根本原因(There is no root cause)。我们最好不 要再去试图寻找根本原因了,这样做只是在攀登文化期望(cultural expectations)和强 大且根深蒂固的心理本能(psychological instincts)的悬崖峭壁。
20 世纪 80 年代奏效的流程和方法论,到了 90 年代已略显落后,现在更是完全不适用了 。我们正在探索新的领地和模型,以构建、部署和维护软件 —— 以及开发软件的组织自身( organizations themselves) 。