编者按:本文来自微信公众号“InfoQ”(ID:infoqchina),作者 Gergely Orosz,译者 足下,策划 蔡芳芳,36氪经授权发布。
在过去的几年中,我一直在构建和运营大型分布式系统:Uber 的支付系统。在这段时间里,我学到了许多分布式架构的概念,也亲眼看到了高负荷高可用系统的构建和运营是多么富有挑战性。就构建系统这件事来说,它本身是非常有趣的。
要规划好当流量增加 10 倍 /100 倍时系统该如何处理,即使硬件出故障数据也要能持久化保存等等,解决这些问题都可以让人有极大的收获。不过对于我个人来说,运营一套大型分布式系统却更是令人大开眼界的经历。
系统越大,墨菲定律“可能出错的事终将出错”就越适用。对于频繁进行系统部署、许多开发者协同提交代码、数据分散在多个数据中心、系统由全球海量用户共同使用这样的场景来说,就更明显。在过去的几年中,我经历过许多次不同的系统故障,很多都大大出乎我意料之外。
从可预见的问题——如硬件故障或不小心把有缺陷的代码发布到生产系统,到连接数据中心的网络光纤被铲断或多个级联故障同时发生,许多次故障都让部分系统无法正常工作,因而导致了停服,最终造成了极大的业务影响。
这篇文章是我在 Uber 工作期间,关于如何可靠地运营一套大型分布式系统的经验总结。我的经验会很具有普遍性,运营类似规模系统的人也会有类似的经验。
我曾经与谷歌、Facebook、Netflix 等公司的工程师们谈过,他们的经验和解决方案都很相似。不管系统是运行在自建数据中心(Uber 大多数情况下是这样的),还是运行在云端(Uber 的系统有时候会扩展到云端),只要系统规模相似,文章中的想法和流程就会适用。不过如果要把经验照搬到小型系统或非核心系统时就请三思而后行,因为很可能会过犹不及。
具体来说,我会谈到以下话题:
监控
值班、异常检测与告警
停服与事件管理流程
事后总结、事件回顾与持续改进的文化
故障切换演习、有计划的停机、容量规划与黑盒测试
SLO、SLA 及相应的报告
独立的 SRE 团队
对可靠性做持续投入
深入阅读建议
要知道系统是否健康,我们就要回答问题:“我的系统在正常工作吗?”要给出答案,首先就要收集关于系统关键部分的数据。对于运行在不同数据中心多台服务器上,由多个不同服务组成的分布式系统来说,决定哪些关键指标需要被监控,这事本来就不容易。
基础设施健康监控:如果一或多台服务器、虚拟机负载过高,那分布式系统就会发生部分降级。服务要运行在服务器上,所以像 CPU 使用率、内存使用率之类的服务器基本健康信息就很值得监控。
有些平台就是专门做这样的监控的,而且支持自动扩展。Uber 有个很大的核心基础设施团队,专门为大家提供底层的监控和告警。不管具体的实现方案如何,当服务的某些实例或基础设施有问题时你都要被通知到,这些信息必须掌握。
服务健康监控:流量、错误、延迟。“后端服务健康吗”?这个问题实在太泛了。观察终端在处理的流量、错误率、终端延迟等,这些都对服务健康提供着有价值的信息。
我喜欢为它们各自设置专门的仪表盘。构建新服务时,使用合适的 HTTP 响应映射,并监控相应的编码,这些都会提供很多关于系统状态的信息。比如在客户端出错时返回 4XX 编码,在服务端出错时返回 5XX 编码,这类监控很容易构建,也很容易解读。
对系统延迟的监控值得多考虑一下。对于产品来说,目标就是让大多数的终端用户都有良好的体验。但只监测平均延迟这项指标远远不够,因为平均延迟会掩盖一小部分高延迟的请求。因此就经验来说,监测 P95、P99 或 P999(即 95%、99% 或 99.9% 的请求)的延迟指标会更好。
监测这些指标得到的数字可以回答这样的问题:99% 的人发出的请求会有多快得到响应(P99)?1000 个人发出请求,最慢能怎样(P999)?如果有读者对这个话题感兴趣,可以进一步阅读文章《 latencies primer 》。
上图展示的是平均、P95 和 P99 的延迟指标。请注意,尽管平均延迟是低于 1 秒的,但有 1% 的请求花费了两秒或更多时间才完成,这样的事实被平均延迟掩盖了。
关于监控和可观测性的话题可以继续深入挖掘。有两份材料值得细读,一是谷歌的书《SRE:Google 运维解密》,另外是分布式系统监控的“四个黄金信号”。
从中可以知道,如果你的面向用户的系统只想监测四个指标的话,那么关心流量、错误、延迟和饱和度就好了。喜欢吃快餐的话,可以读读 Cindy Sridharan 的电子书《分布式系统可观测性》,里面讲到了其它一些有用的工具,如与事件日志、指标和跟踪等有关的最佳实践。
业务指标监控。监控服务可以让我们了解服务的行为看上去是否正确,但我们无法得知服务的行为是否符合预期,使得“业务可以照常进行”。以支付系统为例,一个关键问题就是:“人们用某种特别的支付方法买单,是不是已经足够可以完成一次旅行?”辨别出服务使能的业务事件,并对这些业务事件进行监控,这是最重要的监控步骤之一。
我们的系统曾经经历了若干次停服,我们深受其害,在发现这样的停服事件没有其它办法可以监控之后,我们团队构建了业务指标监控系统。当停服事件发生时,所有的服务都看起来一切正常,但某些跨服务的核心功能就是失效了。该监控是专门针对我们公司和业务领域而构建的。因此,我们就得花很多心思,以 Uber 的可观测性技术栈为基础,努力地为自己定制这类监控。
监控可以让人们查看系统的当前状态,是个非常有用的工具。但是,要在系统发生故障时自动检测并且发出告警,让人们可以采取相应地行动,监控只是第一步。
值班就是一个更广的话题了:Increment 杂志在这方面做得很棒,对值班问题进行了许多方面的探讨。我特别倾向于把值班做为“谁构建,谁负责”思路的一个扩展。哪个团队构建了服务,那就由哪个团队负责值班。我们团队就负责自己构建的支付服务的值班。因此不管什么时候发生了告警,值班工程师都会响应并对问题进行处理。我们该如何从监控得出警告呢?
从监控数据中发现异常,这是一个巨大的挑战,也是机器学习的用武之地。有许多第三方服务可以提供异常检测功能。而且对于我们团队来说很幸运,我们公司就有机器学习团队可以为我们提供支持,他们专门设计了适合 Uber 用例的解决方案。
位于纽约的可观测性团队还就 Uber 如何做异常检测这个主题写过一篇很不错的文章。从我们团队的角度看,我们会把我们的监控数据推送到他们的管道中,并收到不同信任程度的告警,然后我们再决定要不要自动呼叫某位工程师。
什么时候发出告警也是个很值得探讨的问题。告警太少可能意味着错过处理某些重大故障的时机,告警太多的话值班工程师就无觉可睡了,毕竟人的精力是有限的。因此对告警进行跟踪并分类,并测量信噪比,这对调节告警系统非常重要。对告警信息进行检查,并标记是否需要采取相应的动作,不断减少不需要的告警,这就迈出了非常好的一步,让可持续性的值班行为处于良性循环了。
Uber 内部使用的值班仪表盘例子,由位于维尔纽斯的 Uber 开发者体验团队构建
Uber 位于维尔纽斯的开发者工具团队开发了专门的值班工具,用于对告警进行注解,并对轮班进行可视化。我们团队每周都会对上一周的值班情况进行回顾,分析痛点,并花时间提高值班体验。这件事每周都会做。
设想一下,这周由你值班。半夜里来了个电话告警,你就要调查一下是否发生了停服,是否会影响生产。结果看到系统真的有一部分失效了,谢天谢地,监控和告警的确反应了系统的真实情况。
对于小型系统来说,停服不算什么大麻烦,值班工程师可以判断出发生了什么,以及为什么发生。一般来说他们都可以很快判断问题,并进行解决。而对于使用了多种服务或微服务的复杂系统来说,很多工程师都在向生产提交着代码,因此判断出发生错误的根本原因,这就有相当大的难度了。在这种情况下,如果有些标准的处理流程,事情就会容易得多。
把运行手册附加到告警信息中,描述一下简单的处理步骤,这就是第一层防线。如果团队的手册写得非常好,那么即使值班工程师对系统理解得并不深入,这一般也不会是什么问题。手册要不断更新,当出现新的故障类型时要尽快进行修订。
当服务由多个部门共同构建时,在部门之间沟通故障也很重要。在我们公司里,几千个工程师共同工作,他们会自己选择自认为合适的时机将服务部署到生产环境,因此事实上每个小时都会发生几百次部署。对一个服务的部署可能会影响另一个看起来压根不相关的服务。
因此,是否有清晰标准的故障广播和沟通渠道就成了故障顺利解决的关键。曾经发生过好几个案例,告警看起来与我之前见过的似乎一点关联都没有,幸好我想起来别的团队中有些同事曾经见过类似的古怪问题。在一个大的故障处理群中,我们一起很快就定位出了根本原因并解决掉了。做为一个团队来处理问题,总会比单个经验丰富的员工快得多。
立刻恢复生产,事后调查原因。在处理故障的过程中,我也经常会“肾上腺素激增”,想要直接把问题当场解决掉。一般来说故障都是由某次新部署的代码导致的,变更的代码中会有某些很明显的错误。在以前我一般不会回滚代码,而是直接去修改代码并发布新版本。
但事实上一边生产的故障没有解决,另一边却去修改代码中的缺陷,这实在不是个好主意,这么做收益很小,损失却很大。因为新的修复代码必须尽快完成,那就只能在生产环境进行测试,这么做反而更加容易引入新的缺陷,甚至旧的故障还没解决,新的问题又出现了。
我也曾经见过类似原因导致的一连串的故障。因此请一定记住,要先恢复生产,抵抗住立刻修复或调查根本原因的诱惑。事实上第二天回来上班再仔细调查根本原因也未尝不可。
这里讲的是一个团队如何进行故障的后续处理。他们会继续做调查吗?他们会不会停下手头所有与生产有关的工作,花费惊人的代价来做一次系统级的修复?
事后总结是构建健壮系统的基石。好的事后总结是非常细致和无可挑剔的。Uber 的事后总结模板已经随着时间的推移而演进好多个版本了,内容包括许多小节,有事件总结、大概影响、事件发生过程的关键时间点、根本原因分析、经验总结及一系列的后续跟进内容。
一个与我在 Uber 使用的事后总结模板类似的例子
好的事后总结会对故障根本原因进行深入挖掘,得到改进措施来避免类似的故障发生,或者在下一次出现类似故障时可以快速检测和恢复。我说的深入挖掘,不是说浅尝辄止地知道“这次故障的原因是最近提交的代码引入的缺陷,在代码审查时没能发现出来”就可以了,而是要用“五个为什么”的思考技巧去深入挖掘,最终得出一个更有意义的结论。比如下面的例子:
为什么会发生这样的问题?——> 缺陷是由某次提交的有缺陷的代码导致的。
为什么其他人没能发现这个问题?——> 代码审查者没注意到这样修改代码会导致这种问题。
为什么我们完全依赖代码审查者来发现这样的问题?——> 因为我们没有对这类用例的自动化测试。
为什么这类用例没做自动化测试?——> 因为没有测试账号的话,很难做测试。
为什么我们没有测试账号?——> 因为现在的系统不支持。
结论:现在我们知道了原来错误的根本原因在于没有测试账号,因此会建议让系统支持添加测试账号。做为进一步的跟进措施,要为将来所有类似的代码改动编写自动化测试用例。
事件回顾是事后总结的一个很重要的辅助工具。当许多团队都对事后总结做了很细致的工作时,其他团队就会多了许多可以参考的内容并从中受益,并会设法完成一些预防性改进。一个团队要让别人觉得很可靠,他们提出的系统级改进措施要能得以推行,这一点很重要。
对于特别关注可靠性的公司来说,特别重大的事件回顾都会由有经验的工程师们进行审查,并对其中的关键点提出意见。公司级的技术管理层也要对改进措施加以推进,尤其是对耗时长、可能阻碍其他工作进行的问题。健壮的系统不是一天就能构建出来的,肯定是经过持续改进慢慢迭代出来的。迭代来源于公司级的持续改进文化,要从事件中学到经验。
有许多日常活动都需要巨大的投入,但要想保证大型分布式系统的在线稳定运行,这样的投入又是至关重要的。我加入 Uber 公司之后才第一次遇到了这样的事情,因为我就职的上一家公司不管从规模还是基础设施来说,都不需要我们做这样的事。
我一直认为数据中心的故障切换演习是件非常没意义的事,但亲身经历了几次之后我的观点慢慢发生了变化。我一直认为,设计健壮的分布式系统只要数据中心在失效时能快速恢复就好了。既然设计好了,理论上也行得通,那还为什么要不断地定期尝试呢?实际上答案与规模有关,而且也要测试在新的数据中心流量急剧增涨时,服务是否仍能高效地进行处理。
当切换发生时,我观察到的最常见的场景就是新数据中心的服务没有足够的资源来处理所有新涌入的流量。假设在两个数据中心里分别运行着 ServiceA 和 ServiceB,每个数据中心运行着几十上百个虚拟机,资源利用率是 60%,告警的阈值是 70%。
现在我们做一次切换,把数据中心 A 的流量全都切到数据中心 B 去。在这种情况下假如不部署新的服务器,数据中心 B 肯定处理不了这些流量。部署新服务器可能会需要很长时间,因此请求很快就会开始堆积,并被抛弃。这样的堵塞也会开始影响别的服务,导致其他系统的接连失效,而这些本来与这次切换并不相关。
数据中心故障切换失败的可能示意图
其它可能的失败场景包括路由问题、网络容量问题、后端压力瓶颈等。所有可靠的分布式系统都需要具备在不对用户体验造成任何影响的情况下进行数据中心切换的能力,因此也应该可以进行演习。在这里我要强调“应该”,因为这样的演习是检验分布式系统网络可靠性最有效的方法之一。
有计划的服务宕机演习是检验系统整体可靠性的非常棒的方法,也是发现隐藏依赖和对特定系统的不合理、非预期使用的好方法。对于面向用户、已知依赖比较少的服务来说,做这样的练习相对简单。
但关键核心系统要求高在线时间,或者被许多其他系统所依赖,做这样的练习就没那么直观了。但如果某天这个核心系统真的就失效了,又会怎样呢?因此最好是用一次可控的演习来验证答案,所有的团队都要知道会有不可知的故障发生,并且准备就绪。
黑盒测试用于检验系统的正确性,与终端用户的使用方式最为接近。这种测试与端到端的测试很相近。除此之外,对于大多数产品来说恰当的黑盒测试可以确保他们的投入得到回报。关键的用户流程和最常见的用户界面测试用例,都是很好的选择,可以让黑盒测试得以进行:用一种在任何时间都可能被触发的方式来做黑盒测试,检验系统是否可以正常运行。
以 Uber 为例,一个很明显的黑盒测试用例就是在城市的范围内检测乘车者与司机的匹配是否正常。即在某个特定的城市里,乘车者通过 Uber 发起的请求是否能得到司机的响应,并完成一笔载客生意。当这个场景自动化之后,这个测试就可以在不同的城市定期模拟运行了。有了健壮的黑盒测试系统就可以很容易地验证系统是否可以正常工作,起码是部分系统。黑盒测试对于故障切换演习也很有用,是获取故障切换状态的最快方式。
在一次故障切换演习中进行黑盒测试的例子,故障确认后手动进行回滚。横座标为时间,纵座标为运行失败的用例数
对大型分布式系统来说,容量规划也差不多同等重要。我对大型分布式系统的定义是指那些每个月要支出几万甚至几十万美元的计算与存储系统。对于这样规模的系统来说,相比构建在云上的自动扩展方案,自建系统并进行固定数量的部署成本会更低些。
但自建系统至少要考虑当峰值流量到来时的自动扩展问题,保证对业务流量进行正常平稳的处理。另一个问题是,下个月至少应该运行多少个实例呢?下个季度呢?下一年呢?
对于成熟并精心保存了历史数据的系统来说,对将来的流量进行预测并非难事。另外一件重要的事就是预算,要挑选供应商并锁住云服务提供商的优惠折扣。如果你的服务支出很大,而你又忽略了容量规划,那你就错失了一个非常简单的减少并控制支出的方法。
SLO 意味着服务等级目标( Service Level Objective ),是系统可用性的一个数字化目标。好的实践是为每个独立的服务都定义服务等级目标 SLO,比如容量目标、延迟、准确度、可用性等。这些 SLO 可以用于触发告警。下面是一些服务等级目标的例子:
业务级 SLO 或功能级 SLO 是对上面这些服务的抽象。它们会包括直接影响用户或业务的指标。比如一个业务级目标可以这么定义:希望 99.99% 的邮件可以在一分钟之内确认发送成功。这个 SLO 也可以与服务级 SLO 相对应(比如支付系统和邮件接收系统的延迟),也有可能会用别的方式去进行测量。
服务等级协议(SLA,Service Level Agreement)是服务提供者与服务使用者之间更广泛的约定。一般来说,一个 SLA 会由多个 SLO 组成。比如,“支付系统 99.99% 可用”可以做为一个 SLA,它可以继续分解为各个支撑系统的特定 SLO。
定义了 SLO 之后,下一步就是对它们进行测量并报告。对 SLA 和 SLO 进行自动化的测量和报告,这是个复杂的目标,会与工程和业务团队的优先级相冲突。工程团队不会感兴趣,因为他们已经有了各种不同级别的监控,可以实时地检测故障。业务团队也不会感兴趣,他们更希望把精力用于提交功能,而不是用在一个不会很快产生业务影响的复杂项目上。
这就谈到了另一个话题:已经或即将运营大型分布式系统的公司要为系统的可靠性投入专门的团队。我们谈谈站点可靠性工程团队。
站点可靠性这个词大概在 2003 年出自谷歌,谷歌公司现在已经有了超过 1500 名 SRE 工程师。因为运营生产环境的任务越来越复杂,越来越自动化,所以很快这就成了个全职工作。工程师们会慢慢地全职进行生产自动化:系统越关键,故障也就越多,这件事就会越早发生。
快速成长的技术公司一般会比较早成立 SRE 团队,他们会有自己的演进路线。Uber 的 SRE 团队成立于 2015 年,任务是对系统复杂度进行持续管理。别的公司在成立专门的 SRE 团队同时,也可能成立专门的基础设施团队。当一个公司的跨团队可靠性工作需要占用多个工程师的时间时,就可以考虑成立专门的团队做这件事了。
有了 SRE 团队,他们就会从运营的角度考虑,让运营大型分布式系统的工作变得更轻松。SRE 团队可能会有标准的监控和告警工具。他们可能会购买或自己构建值班相关工具,可以为值班的最佳实践提供建议。他们会为故障回顾提供帮助,会构建系统来让大家更容易地检测、恢复和预防故障。他们也会主导故障切换演习,推动黑盒测试,并参与容量规划。他们会驱动选择、定制和构建标准工具,来定义和测量 SLO,并进行上报。
不同的公司需要 SRE 团队解决不同的痛点,所以不同公司的 SRE 团队之间也很可能并不相同。它们的名称也经常会不同:可能会叫运营团队、平台工程团队或基础设施团队。关于站点可靠性,谷歌免费发布了两本必读书,想深入了解 SRE 的话可以读一下。
不论构建什么产品,完成第一版只是个开始。在第一版之后,新功能会通过后面的迭代不断加入。如果产品很成功,可以带来商业上的回报,迭代工作就会不断继续。
分布式系统也有类似的生命周期,而且需要不断地进行投入。不只是开发新功能,还要满足扩展的需求。当系统要承担更大的负载、存储更多数据、从事相关工作的工程师越来越多时,就需要持续关注,才能让它一直平稳地运行。许多第一次构建分布式系统的人都会把它想像成一辆车:投入使用后,只需要隔几个月做一次定期保养就可以了。从系统运行的角度来说这个对比行不通。
我更愿意把运营一个分布式系统所要花费的功夫类比成运营一个大型组织,比如一家医院。要让一家医院运营良好,就要不断地做验证和检查(监控、告警、黑盒测试等)。
新员工和新设备会不断投入进来:对医院来说就是医生和护士等员工,以及新型医疗仪器之类的设备;对分布式系统来说就是加入新员工,上线新服务。随着人和服务的数量不断增长,旧的做事方式开始变得不够高效:可以想像,乡镇里面的小诊所和城市里的大医院运营方式肯定是不同的。
因为要用更高效的方式做事,所以产生了全职工作,对效率的测量和报告也变得很重要。因此大型医院就会有更多的支撑型员工,比如财务、人力资源和安保;运营大型分布式系统也要依靠基础设施、SRE 等支持团队。
要让一个团队可以运营好一个可靠的分布式系统,公司要对运营做持续投入,不只是这些系统本身,还有构建它们的平台。
尽管这篇文章的内容已经够长了,但它仍然只谈到了皮毛而已。要想深入理解如何运营分布式系统,我推荐下面这些内容:
书
《SRE:Google 运维解密》:来自谷歌的非常棒而又免费的书。其中“监控分布式系统”一章的内容与本文密切相关。Cindy Sridharan 写的《分布式系统可观测性》:这是另一本非常棒的免费书,就监控分布式系统提出了许多非常好的观点。
Martin Kleppmann 博士写的《设计数据密集型应用》:这是迄今为止我所找到的讲分布式系统概念讲得最好的一本书。不过,关于本文内所讨论的运营方面的内容,这本书讨论得不多。
在线资源
Increment 杂志的值班话题:这是一系列的文章,内容聚焦于亚马逊、Dropbox、Facebook、谷歌和 Netflix 等公司的事件响应流程。
学习构建分布式系统:这是亚马逊工程师 Marc Brooker 写的贴子,回答了“我该怎么学着构建大型分布式系统”的问题。
本文翻译自“ Operating a Large, Distributed System in a Reliable Way: Practices I Learned ”,翻译已获得原作者 Gergely Orosz 授权。原文链接:
https://blog.pragmaticengineer.com/operating-a-high-scale-distributed-system/