一切系统都是分布式的


【编者的话】本文内容来自 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 。

但这件事的外延要有趣地多。考虑一下目前发展地如火如荼的自动驾驶汽车。自动驾驶消除了人为失误这个背锅侠,那我们得到的结论将是:在很多方面,自动驾驶汽车要比传统汽车更加安全。 但事实真是这样吗?考虑下面的情况:
  1. 如果发生了完全在汽车自动驾驶系统控制之外的事将会怎样?
  2. 如果训练汽车识别红绿灯的数据有错误怎么办?
  3. 如果 Google 地图让它去做一些明显很愚蠢的事,并且这些事很危险怎么办?


我们已经到达了软件开发中的一个特殊点 —— 不管是在技术上还是在社会/组织上,到了这个点我们不再能理解、看到、或控制系统的所有组件 —— 我们的软件正在变得越来越复杂和分布式。软件行业本身已经变成一个分布式的、复杂的系统。

我们如何开发和管理那些庞大到无法理解、复杂到无法控制、出错方式也无法预测的系统?

拥抱故障

分布式系统曾经只是计算机科学博士和软件架构师的领地,受众非常小。但现在不同了。 仅仅因为你在笔记本电脑上写程序、无需关心消息如何传递和锁问题,并不意味着你不需要关心分布式系统:
  1. 你写的程序发起了多少对外部服务的 API 调用?
  2. 你的代码是跑在 PC 上还是移动设备上——你确切地知道所有可能的设备类型吗?
  3. 当你的应用正在运行时,它可能遇到哪些网络方面的限制,关于这些你知道多少?
  4. 当软件到达特定规模时,它会遇到哪些瓶颈,关于这些你又知道多少?


在经典分布式计算理论中,我们学到的一件事情是:分布式系统经常会发生故障,而且大都是局部而非全局故障。这些故障不仅难于诊断和预测,而且很难复现——可能是某个特定的第三方数据流没数据了,可能是位于某个你从未听说过的地方的路由器挂掉 了。你永远在同短时故障(intermittent failure)作斗争,这注定是一场失败的战役吗?

应对复杂分布式系统的方法并不是简单地增加测试,或者采用敏捷开发流程,也不是采用 DevOps 或者持续交付(continuous delivery)。任何单一的技术或方法都无法阻止类似丰田汽车事故这样的事情再次发生。实际上,类似这样的事情肯定会再次发生。

解决这类问题我们需要拥抱这样一种观念:无法预知的故障种类太多了——我们面对的是一 片巨大而未知的未知海洋;此外,还需要改变我们构建系统时——以及运维现有系统时——的思考方式。

分布式设计,本地化开发

好了,现在我们可以确定的一点是:每个编写或开发软件的人都需要像分布式系统工程师一样去思考。但这句话到底意味着什么?在实际中,它意味着:丢弃那种单计算机(节点)的思考模式(single-computer mode of thinking)。

直到最近,我们才可以将计算机视为一个相对确定性的东西(a relatively deterministic thing)。当编写一个在某台机器上运行的代码时,我们能够确定性地假设很多东西,例如,内存查询的方式。但现在已经没有应用还运行在单台机器上了——云就是这个时代的计算机(the cloud is the computer now),它就像一个生命系统(living system),一直在持续不断地变化,尤其是在越来越多的公司开始采用持续交付这种新范式的过程中。

因此,你必须开始:
  1. 接受这样的假设:支撑你的软件运行的系统一定会发生故障
  2. 对为什么会发生故障以及故障可能会以怎样的形式发生做出预案
  3. 针对这些预案设计数据收集方案


这并不是像说一句“我们需要更多测试”那么简单。传统的测试哲学中,假定 所有测试用例都是能够描述出来的,但在分布式系统中这一点不再成立。(这并不是说 测试不重要了,而是说测试不再是万灵药。)

当处于一个分布式环境、并且大部分故障模式都是无法提前预测也无法测试时,监控就成了唯一的理解应用行为的方式。

数据是分布式系统的通用语言

如果对刚才的比喻(复杂系统就像一个生命系统)进行延伸,那在诊断出一个人中风后才去寻找病因与在中风前就能及早发现问题明显是两种方式。你当然可以翻阅病例上的就诊记录,从中看出其实早有中风的苗头,但你更需要的是一个早期告警系统,以及一种在问题刚发生时就能看到并尽可能快地介入处理的方式。

另外,历史数据只能告诉你哪里出了问题,并且是局限在特定时间段内的问题。但在处理分布 式系统相关的问题时,需要关心的事情要比仅仅 ping 一下服务器通不通多多了。

与测量和监控相关的工具现在已经有很多,这里不会就具体工具展开讨论,而是要告诉你:在查看自己的应用和系统的监控数据的过程中,你会对“直方图通常比平均值更能说明问题”有越来越深的理解,在这个过程中开发者不会再将监控视为纯粹是系统管理员的领域。

复杂系统中人的角色

无论多么复杂的软件最终都是人写出来的。

任何对分布式系统和复杂度管理的讨论最终都必须承认人在我们设计和运行的系统中的角色。人是我们创造出来的复杂系统中不可分割的一部分,而且很大程度上我们要对他们的多样性(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)。

原文链接:https://arthurchiao.github.io/ ... d-zh/

0 个评论

要回复文章请先登录注册