ArthurChiao's Blog

[译] 一切系统都是分布式的(OReilly, 2015)

Published at 2020-01-26 | Last Update 2020-01-26

译者序

本文内容来自 2015 年的一本小册子 Everything is distributed(下载 Free-OReilly-Books), 其中集合了 5篇与性能和运维相关的文章,本文翻译其中第二篇 Everything is distributed

这篇文章思考有一定深度,但部分观点恐怕失之颇偏,比如作者认为分布式系统中的故障没 有根本原因(There is no root cause)、查找 root cause 多半是徒劳等等。

本文内容仅供学习交流,如有侵权立即删除。

由于译者水平有限,本文不免存在遗漏或错误之处。如有疑问,请查阅原文。


目录

  1. 拥抱故障
  2. 分布式设计,本地化开发
  3. 数据是分布式系统的通用语言
  4. 复杂系统中人的角色

以下是译文。


物流中心(图片来源)

人们应该感到惊讶的并不是每天都有这么多故障,而是每天只有这么少故障。你不应该惊 讶于自己的系统偶尔会崩溃,而应该惊讶于它竟然能长时间不出错地运行。

— Richard Cook

2007 年 9 月,76 岁的 Jean Bookout 正在 Oklahoma 一条陌生的道路上驾驶着她的丰田 凯美瑞,她的朋友 Barbara Schwarz 坐在副驾驶的位置。突然,这辆汽车自己开始加速。 Bookout 尝试了踩刹车、拉手刹,但都不管用,汽车还是继续加速。最后这辆车撞上了路堤 ,造成 Bookout 受伤,Schwarz 死亡。在随后的法律程序中,丰田的律师将事故原因指向 此类事故最常见的罪魁祸首:人为失误(human error)。“人们有时会在开车时犯错” ,其中一位律师宣称。Bookout 年纪很大了,而且也不是她熟悉的路,因此造成了这场悲剧 。

然而,近期一个针对丰田的 产品可靠性测试 却令这件事情有了一个 180 度的大转弯:凯美瑞中的一个软件 bug 导致的栈溢出错误( stack overflow error)才是此次事故的罪魁祸首。下面两方面原因使得这一事件非常重要:

  1. 此类事故最常见的背锅侠 —— 人为失误 —— 最后确认并不是造成这次事故的原因( 这个假设本身就是有问题的
  2. 这件事展示了我们如何从 一个软件错误导致的小故障或(潜在更大的) 公司营收损失,无缝跨越到了 人身安全 的领域

要将这件事情往小里说可能也容易:(目前)在某款特定车型搭载的软件中似乎发现了一个常见 bug 。

但这件事的外延要 有趣地多。考虑一下目前发展地如火如荼的自动驾驶汽车。自动驾驶消除了人为失误 这个背锅侠,那我们得到的结论将是:在很多方面,自动驾驶汽车要比传统汽车更加安全。 但事实真是这样吗?考虑下面的情况:

  1. 如果发生了完全在汽车自动驾驶系统控制之外的事将会怎样?
  2. 如果训练汽车识别红绿灯的数据有错误怎么办?
  3. 如果 Google 地图让它去做一些明显很愚蠢的事,并且这些事很危险怎么办?

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

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

1. 拥抱故障

分布式系统曾经只是计算机科学博士和软件架构师的领地,受众非常小。但现在不同了。 仅仅因为你在笔记本电脑上写程序、无需关心消息如何传递和锁问题,并不意味着你不 需要关心分布式系统:

  1. 你写的程序发起了多少对外部服务的 API 调用?
  2. 你的代码是跑在PC 上还是移动设备上 —— 你确切地知道所有可能的设备类型吗?
  3. 当你的应用正在运行时,它可能遇到哪些网络方面的限制,关于这些你知道多少?
  4. 当软件到达特定规模时,它会遇到哪些瓶颈,关于这些你又知道多少?

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

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

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

2. 分布式设计,本地化开发

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

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

因此,你必须开始:

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

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

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

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) 。