第 1 章 生产环境的生存法则

第 1 章 生产环境的生存法则

在项目上努力工作了许久,看起来所有的特性都已实现,甚至大多数特性通过了测试。可以松一口气了,完成任务了。

真的完成任务了吗?

“特性已实现”是否就意味着“就绪可上线”?系统是否真的万事俱备只待部署?是否真正可以放心地将系统移交给运维部门,并且任其面对现实世界中的大批用户?此时,你是否开始有一种即将面临深夜紧急电话和系统告警的沮丧情绪?事实证明,软件开发除了添加所需特性之外,要做的事情还多着呢!

如今,学校里的那些软件设计课程极其片面。这些课程只是讨论系统“应该”做什么,却没有解决相反的问题——系统“不应该”做什么。系统不应该崩溃、停止响应、丢失数据、侵犯隐私、损失金钱、摧毁公司,或者“杀死”客户。

项目团队的目标往往是通过QA1部门的测试,而不是通过生产环境的生存考验。也就是说,团队的大部分工作是想方设法地通过测试。但即使是做了敏捷、务实和自动化的测试,也不足以证明软件已经为面对现实世界准备就绪。来自现实世界的压力和重负——包括亢奋狂热的真实用户、全局范围的网络流量,以及在闻所未闻的国家编写病毒软件的人群——远远超出了所能期望的测试范围。

1QA是quality assurance的缩写,即质量保证,本书指软件测试。——译者注

请面对现实:计划再周详,仍会出状况。当然,尽可能防患于未然总是好的。但是,误以为自己已经预见和消除了所有可能的不良事件并能万事大吉,这是最要命的。一方面要采取行动以预防那些能够预防的事情,另一方面要确保系统在整体上能够从任何未曾预料到的重创中恢复过来。

1.1 瞄准正确的目标

大多数软件是为软件开发实验室或QA部门的测试工程师设计的。其设计和构建的目的是通过类似这样的测试——“顾客的姓氏和名字都是必需的,但中间名的首字母是可选的”。这些软件的目标,能在QA部门所营造的人工环境中(而不是在现实世界的生产环境中)正常运行。

今天的软件设计类似于20世纪90年代早期的汽车设计,它们都与现实世界脱节。那些汽车仅仅在凉爽舒适的实验室中设计,模型和CAD图纸展示看起来都很绝妙。那些汽车给人完美的曲线感,在巨型风扇前闪闪发亮,在稳定气流中低鸣。在实验室这种宁静空间里,设计师所做出的设计既是那么地优雅、复杂、精巧,又是那么地脆弱、不中用,最终早早被淘汰。如今大多数的软件架构和设计,同样在干净、稳定却与现实相差甚远的环境中进行。

一辆汽车看起来很漂亮,但它在路上能够行驶的时间少于在店里陈设的时间,这种车有人要吗?当然没人要!人们想要的是专为现实世界设计的汽车,它的设计师应该知道:当汽车行驶里程超过5000千米后一般要更换机油2;即使轮胎的花纹深度磨损到1.6毫米,也必须和新胎一样工作良好3;当人们在车上一手拿着早餐,一手拿着手机时,有可能会猛踩刹车。

2此为以前的说法,现在的说法有所改变。——译者注

3当花纹深度磨损到低于1.6毫米时,必须更换轮胎。——译者注

当系统通过QA测试后,是否就能拍着胸脯说系统已为进入生产环境做好了准备?仅通过QA测试并不能证明系统在未来3~10年的适用性。这个系统可能是软件中的丰田凯美瑞,可以连续正常运行数千小时,或者可能是软件中的雪佛兰Vega(该车第一次在公司试验道路上试车时,车身前部就折断了),或者是软件中的福特平托车(一款连平常追尾都易起火爆炸的汽车)。几天甚至几周的测试,不可能说明系统未来几年会怎样。

制造业的产品设计师一直在追求“可制造性设计”。这种设计产品的工程方法能够让人们以低成本和高质量的方式制造产品。而在这个时代之前,产品设计师和制造商都各自生活在自己的世界里。那些无法拧动的螺钉、容易混淆的零部件,以及本可利用现成零件进行替代的定制零件,都体现出这两个世界缺乏交流。这些设计不可避免地导致了低质量和高成本。

今天,我们正面临类似的状况。我们无法交付合格的新系统,因为我们一直在接听请求技术支持的电话,试图解决之前匆忙交付的不完善的系统所遭遇的问题。软件行业的“可制造性设计”,就是“为生产环境而设计”。我们虽然不会将设计交给制造商,但会将完成的软件交给信息技术运维部门。我们既需要设计一个个彼此独立的软件系统,也需要设计由相互依赖的系统所组成的整个生态系统,从而以低成本和高质量的方式进行运维工作。

1.2 应对不断扩大的挑战范围

在客户-服务器系统盛行的那段轻松悠闲的日子里,系统的用户也就几十或几百人,并发用户最多也只有几十个。今天,我们通常会发现,活跃用户的数量大于整个大洲的人口数量。这指的可不只是南极洲和大洋洲!第一个拥有10亿用户的社交网络已经诞生,而且这绝对不会是最后一个。

对系统正常运行时间的要求也提高了。相比曾经主机与其系统管理员间著名的“五个9”4运行时间,现在即便是最普通的商务网站,也期望能达到“24乘7乘365”。(这个说法一直困扰着我。作为工程师,我认为应该要么说“24乘365”,要么说“24乘7乘52”。)显然,考虑到软件如今的规模,我们已经取得了长足的进步。但是随着用户触点的增加和系统规模的扩大,系统遭到破坏的方式也会翻新,环境会变得更加恶劣,人们对缺陷的容忍度会变得更低。

4X个9是衡量系统可靠性的标准,X代表数字3~5,分别是99.9%、99.99%和99.999%,表示一年中系统可以正常的使用时间与总时间之比。三个9表示该系统在连续运行一年时间里最多可能的业务中断时间是8.76小时;四个9对应52.6分钟;五个9则对应5.26分钟。——译者注

这个正在不断扩大的挑战范围,即以低成本快速构建和运维对用户有益的软件,要求我们持续改进架构和设计技术。当把适用于小型WordPress网站的设计,应用于大规模的分布式事务系统时,会出现重大系统故障。后文会谈一些重大系统故障的案例。

1.3 多花5万美元来节省100万美元

很多事情都岌岌可危:项目的成功、股票期权或分红、公司的生存,甚至手中的饭碗。那些以通过QA测试为目标的系统,通常需要持续的高昂投入,进行后期运维、停机修复和软件维护。这样的系统永远都无法实现盈利,更不要说帮助公司获得净利润(只有当系统所创造的收入超过其自身的构建成本时,才能实现)。这些系统所表现出的低可用性,会直接导致公司收益受损,同时品牌形象受到玷污,从而造成间接损失。

在忙碌的软件开发项目中,很容易做出优化开发成本而忽视运维成本的决策。但只有在团队的预算和交付日期都固定的情况下,这样做才有意义。而从甲方花钱开发软件的角度看,这是一个糟糕的决策。如果不取消或废止系统,至少要满足在其整个生命周期中,运维时间远远超过开发时间。为了节省一次性的开发成本,却耗费无尽的运维成本,这样做没有意义。事实上,从财务角度看,反其道而行之会更有意义。假定每次发布需要5分钟的停机时间,该系统会使用5年,并且每月更新版本(虽然大多数公司希望能够更频繁地发布新版本,但此处仅做非常保守的假设),这样就可以计算该系统由停机时间造成的含资金时间价值折扣的预期成本。这样计算下来,成本大概为100万美元,300分钟的停机时间,约每分钟按3300美元保守成本计。

现在假设可以投资5万美元来创建不停机发布的构建流水线和部署过程。这样做至少可以避免100万美元的损失,而且大有可能提高系统部署频率,占领更多市场份额,但是目前阶段的直接收益尚不足以体现。面对20倍的投资回报率,大多数首席财务官不会介意花费区区5万美元!

设计决策和架构决策,也是财务决策。在选择时,必须着眼于实施成本和下游成本。从技术和财务的视角综合看问题,是本书反复出现的一大要点。

1.4 让“原力”与决策同在

早期决策会对系统的最终形态产生巨大的影响。最早做出的决定可能是最难以反悔的。那些关于系统边界和子系统划分的早期决策,影响着团队结构、资金分配、项目集管理结构,甚至考勤表代码。团队的任务分派决定了系统架构的雏形。非常具有讽刺意味的是,早期决策恰恰是在信息最不完备的时候做出的。团队在启动项目时,往往最不了解软件的最终架构,却偏偏要在那时必须做出一些最不可能更改的决定。

必须承认,我是敏捷开发的支持者。注重尽快交付和渐进式改进,这种理念能让软件快速上线。产品化是了解软件如何响应现实世界请求的唯一途经,因此我支持所有能够加快产品化进程的方法,从而尽可能早地了解软件。即使是在敏捷项目中,最好也要富有远见地制定决策。这好比设计师必须使用“原力”5去看未来,才能选择最稳健的设计。虽然不同的设计方案通常具有相近的实施成本,但这些方案在整个软件生命周期中的总成本截然不同。因此,考虑每个方案对系统可用性、系统容量和灵活性的影响至关重要。本书通过列举采用不同方案的具体示例,展示数十种设计方案产生的下游效应。这些例子都来自我所遇到过的真实系统,其中大多数曾让我辗转难眠。

5作者借用科幻系列电影《星球大战》所虚构的“原力”(一种超自然而又无处不在的神秘能量场),来比喻软件发布与现实世界的互动关系。——译者注

1.5 设计务实的架构

一般来说,存在两种架构,其中一种侧重对系统更高层次的抽象,以便于跨平台移植,并且基本不会与诸如硬件、网络、电子和光子这些难以处理的细节产生联系。这种架构的极端形式产生了下面描述的“象牙塔”。在极具库布里克风格的整洁房间里,坐着冷漠的大人物,房间的每面墙上都装饰着一个个方块和箭头。大人物在象牙塔中给埋头苦干的程序员下达命令:“中间件应该永远用JBoss!”“所有的用户界面都应该使用Angular 1.0来构建!”“现在、过去以及将来所有的一切,都要永远保存到Oracle中!”“不应该使用Ruby语言!”如果有人曾经咬紧牙关编写符合“公司标准”的代码,而使用其他技术来处理却能轻松10倍,那么他就是象牙塔架构师的受害者。没心思倾听程序员心声的架构师,肯定也没心思听取用户的意见。这样的结果已经有目共睹:当系统崩溃时,用户会为此欢呼,因为至少他们可以有一段时间不必使用它了。

相比之下,另一种架构师不仅会和程序员接触,而且也把自己当作程序员。这种架构师会毫不犹豫地审视一个个抽象,如果发现不适用就会舍弃。这样务实的架构师更可能讨论诸如内存使用情况、CPU的需求、带宽的需求,以及超线程和CPU绑定的优缺点等问题。

象牙塔架构师最享受绝对完美的最终状态,但务实的架构师会不断思考动态变化:“如何在不重新启动的情况下进行部署?”“需要收集哪些指标?如何对它们进行分析?”“系统的哪个部分最需要改进?”

当象牙塔架构师的工作完成之后,系统不能再做任何改进,系统的每个部分都将完美地发挥其功能。与之相反,务实的架构师所设计的系统,其中每个组件都足以满足当前的负荷。并且,当负荷随着时间的推移发生变化时,架构师知道要替换哪些组件。

如果你是务实的架构师,那么就能在本书各个章节中收获丰富的信息。如果你是象牙塔架构师,并且能够坚持读到这里,本书可能使你在对系统不同层次的抽象中走出象牙塔,重新接触和理解“以产品化为归宿”——软件、硬件和用户三者之间至关重要的交集。当系统最终发布时,架构师、用户和公司都将会更加快乐!

1.6 小结

软件只有产品化才能产生价值。开发、测试、集成和规划……这些在产品上线前所做的一切,都仅仅是前奏。从系统的最初发布,到持续成长,再到不断演化,本书会讨论产品化的这一系列阶段。第一部分将讨论稳定性,为了更好地理解如何避免软件崩溃所导致的各种问题,让我们首先看看造成航空公司停飞的软件缺陷。

目录