[译] 可靠地运维一个大型分布式系统:我的学习实践

6,390 阅读23分钟

可靠地运维一个大型分布式系统:我的学习实践

在过去的几年里,我一直在构建和运维一个大型分布式系统:Uber 的支付系统。期间我学到了很多分布式架构概念的知识,并亲眼目睹了高负载和高可用的系统不仅在构建上,还在运维过程中充满挑战。构建这个系统本身是项有趣的工作。安排系统如何应对 10 倍或 100 倍的流量增长,在任何硬件故障都保证数据的持久化(durable),都对智力有所裨益。然而对我来说,运维一个大型分布式系统也是一段大开眼界的经历

系统越大,墨菲定律 —— “凡是可能出错的事就一定会出错” 就越可能应验。这对于被众多开发人员频繁部署、涉及多个数据中心、被全球大量用户使用的系统来说,尤其如此。过去的几年里,我经历了各种系统故障,其中有很多令我感到惊讶。它们有些是可预测的事情,如硬件故障或非恶意的 bugs 进入生产环境,还有些则是网络电缆被切断,或多个级联故障同时发生。我经历了许多次中断(outages),系统的某些部分无法正常工作,产生了巨大的业务影响。

这篇文章是我在 Uber 工作时,可靠地运维一个大型系统的有效实践的集合。我的经验不是独一无二的 —— 在类似规模的系统上工作的人有着相似的经历。我和来自 Google,Facebook 和 Netflix 的工程师聊过,他们分享了类似的经验和解决方案。无论是在自己的数据中心(如 Uber 的大多数情况)或是在云端(Uber 有时扩展到),这里列出的观点和流程,应该适用于类似规模的系统。但是,对于规模较小或关键任务较少的系统而言,这些实践可能会矫枉过正。

监控

要知道系统是否健康,我们需要回答“我的系统是否正常工作?”的问题。为此,收集系统关键部分的数据至关重要。分布式系统在多台机器和数据中心上运行多个服务,很难决定什么才是真正需要监控的关键事项。

基础设施健康监测 如果一个或多个机器/虚拟机过载,分布式系统的一部分可能降级(degrade)。服务所在的机器的运行状况统计信息 —— 它们的 CPU 利用率、内存使用 —— 是值得监控的基础信息。有些平台提供开箱即用的这种监控并能自动扩展实例。在 Uber,我们有一支优秀的核心基础设施团队提供开箱即用的基础设施监控和警报。无论这如何实现,有必要知道实例或服务的基础设施什么时候处在警戒状态。

服务健康监控:流量、错误、延迟 “这个后端服务是否健康?”是很常见的问题。观察需要到达端点的流量大小,错误率和端点延迟等事项,都为服务健康状况提供了有价值的信息。我更喜欢使用仪表板观察这些信息。当构建新的服务时,使用正确的 HTTP 响应映射并监控相应的状态码可以了解系统的情况。因此,保证客户端错误时返回 4XX 状态码映射,服务器错误时返回 5XX 状态码,这种监控将易于构建且易于理解。

监控延迟应该多考虑一下。对于生产环境下的服务,目标是为了大多数最终用户获得良好的体验。事实证明,测量平均延迟不是个好的办法,因为平均值可能会掩盖一小部分高延迟的请求。测量 p95、p99 或 p999 —— 即第 95、99 或 99.9 百分位上的请求延迟 —— 是个更好的指标。这些数据有助于回答诸如“99% 的人请求有多快?”(p99)或“1000 人里最慢的一个请求有多慢?”(p999)的问题。如果对这个话题感兴趣,这篇延迟入门文章挺不错的,值得一读。

可视化平均值,p95 和 p99 的延迟。请注意,尽管这个端点的平均延迟在 1s 以内,还是有 1% 的请求需要 2s 或更长的时间才能完成 —— 这就是测量平均值可能掩盖的信息。

关于监控和可观察性,还有很多可以深入探讨的内容。这里有两个值得阅读的资源。一个是 SRE:Google 运维解密里面关于分布式系统监控的 4 个黄金指标的部分。他们建议,如果你只能测量四个面向用户的系统的指标,请关注流量、错误、延迟和饱和度。另一个是来自 Cindy Sridharan分布式系统可观察性的电子书,这本书也涉及了其它有用的工具,比如事件日志,指标和请求跟踪最佳实践。

业务指标监控 虽然服务健康监控让我们了解服务是否看上去正常,但它完全无法说明是否服务按预期工作,即是否能够“正常经营”。以支付系统为例,关键问题是“用户是否可以使用特定的支付方式来出行?”。识别服务激活的业务事件并监控这些业务事件,是监控最重要的步骤之一。

我的团队所有的服务看上去都在正常运行,然而其中有些服务的关键功能却失效了。在被无法检测的中断打击之后,我们建立了业务指标监控。对于我们的企业和领域来说,这种监控需要量身定制。因此,我们付出了大量的思考和努力,在 Uber 的可观察性技术栈上为我们自己定制这类的监控。

值班待命、异常检测、警报

监控是工程师用于检查系统当前状态的一个很好的工具。但是它实际上像一块垫脚石,只是自动检测出什么时候出现了问题并报警,使得工程师可以采取后续行动。

值班待命(Oncall)本身就是一个广泛的话题 —— Increment 杂志做了很棒的工作,在它的On-Call 这期杂志涵盖了各个方面的内容。我很喜欢将值班待命视为“你搭建,故你拥有”这一观念的进一步行动。建立服务的团队拥有这些服务,也拥有值班待命的责任。对于我们构建的支付系统服务,我的团队拥有值班待命的责任。因此无论何时发出警报,值班待命的工程师将会响应并查看详情。那么我们如何将监控转化为警报呢?

检测监控数据中的异常是艰巨的挑战,也是机器学习可以大放异彩的领域。有许多的第三方服务提供了异常检测。我们的团队很幸运,可以和内部机器学习团队协作,他们为 Uber 的用例定制解决方案。纽约的可观察性团队写了一篇有关异常检测如何在 Uber 工作的有用的文章。从我的团队角度来看,我们把监控数据推送到这支团队的管道(pipeline),并获得各自置信水平的警报。接着我们决定是否要向工程师报警。

何时发出警报是个有趣的问题。警报太少可能导致错过有影响的中断;太多又可能导致不眠之夜,使人心力憔悴。在调整警报系统时,追踪和分类警报,以及测量信噪比非常重要。标记警报是否可付诸行动,然后采取措施减少非可付诸行动的警报,是实现可持续的 on-call 轮值的很好的一步。

Uber 内部使用的值班待命仪表板示例,由来自维尔纽斯的 Uber 开发者体验团队构建。

维尔纽斯的 Uber 开发者工具团队构建了简洁的值班待命工具,我们用它来标记警报,以及让轮班可视化。我们的团队每周都会回顾上次的轮班情况,分析痛点并花时间提升值班待命的体验,周而复始。

中断和事故管理流程

想象一下:你是这周的值班待命的工程师。一个警报半夜把你叫醒。你调查了是否有生产环境的中断发生。呃,似乎是系统的一部分停止运行了。接下来该怎么办?

对于小型系统而言,中断或许不算什么大事,因为值班待命的工程师可以了解发生的问题以及原因。这些问题通常易于理解和缓解。对于具有多个(微)服务,许多工程师提交代码到生产环境的复杂系统来说,仅仅是准确地找到潜在问题的位置就颇具挑战。我们可以通过一些标准的流程来改变这种情况。

运维手册(Runbooks) 通常附在警报上,描述了简单的缓解步骤,作为第一道防线。对于有着良好运维手册的团队,值班待命的工程师即使对系统了解不深,也很少出现问题。运维手册需要不断升级、保持最新版,并在有新的缓解方案时重新修订。

当有很多支团队部署服务时,在组织里沟通中断的情况变得非常重要。在我的工作环境,成千上万的工程师在他们认为合适的时候在生产环境部署服务,这意味着每小时有上百次的部署发生。服务中的一次看似无关的部署可能会影响到另一个服务。这种情况下,标准化的中断广播和交流频道将会有巨大的作用。我多次遇到从未见过的警报 —— 接着发现其它团队里的人也看到了类似的奇怪警报。在一个为中断而设的中心聊天群里,我们能够准确地找到导致中断的服务,并快速地缓解这个问题。相比各自为战,我们作为一个团体可以更迅速地完成任务。

立刻缓解,明天调查。当处于中断过程中,我通常有种必须修好错误的冲动。错误的根源通常是一次失败的代码部署,代码修改中有一个明显的 bug。在过去,我将会直接修好 bug 并推送修复来解决中断,而不是直接回滚代码。然而,在一次中断过程中解决根本问题是一个糟糕的想法。这几乎没有收益,并且一个新修复可能带来更多的损失。因为这意味着必须快速完成新修复,并且只能在生产环境里测试。这可能导致第二个 bug 或第二次中断。我曾见过中断像这样失去控制。请专注于缓解最重要的问题,遏制住自己修复或调查根本原因的冲动。详细的调查可以放在下个工作日做。

事后总结、事故回顾和持续改进的文化

一支团队在中断之后如何应对是很重要的。他们大惊小怪了吗?他们做了小的调查吗?他们是不是花了的大量精力进行后续工作,停止产品功能以进行系统级别的修复?

做好事后总结是构建健壮的系统的基石。一个好的事后总结是免责的,也是详尽的。Uber 的事后总结模版随着时间进化,它包含了事件摘要、影响概述、事件发展的时间表、根本原因分析、经验教训以及一份详细的后续行动清单。

一个类似于我在 Uber 使用的事后总结模版

优秀的事后总结深入研究根本原因,并提出改善措施,使防止、检测或缓解所有类似的中断变得更快。深入研究根本原因,并不仅停留在代码审查人员没能发现代码修改有 bug 这个层面上。而是使用 5 个为什么的探索技巧来深入研究,得出一个更有意义的结论。举个例子:

  • 为什么这个问题会发生? --> 因为含有 bug 的代码被提交。
  • 为什么这个 bug 没有被其它人发现? --> 因为代码审计人员没有发现代码修改会导致这个问题。
  • 为什么我们只依赖于一个代码审计人员发现这个 bug? --> 因为我们没有为这个用例提供自动化测试。
  • 为什么我们不为这个用例提供自动化测试? --> 因为在没有测试账户的情况下测试很困难。
  • 为什么我们没有测试账户? --> 因为这个系统暂时不支持。
  • 结论:这个问题指出了没有测试账户的系统性问题。建议为系统添加测试账户的功能。接着,为相似的代码修改写好自动化测试。

事故回顾是事后总结的重要协同工具。当许多团队完成了彻底的事后总结,其他团队可以在额外的投入中获得受益,并在预防性的改进上受到挑战。团队必须有权执行他们提出的系统级改进,并承担责任。

对那些严肃对待可靠性的组织而言,经验丰富的工程师会审查和质询最严重的事故。需要组织级别的工程管理来授权修复 —— 特别是当它们很耗时并且阻碍其它工作时。健壮的系统不是一夜建成的,而是通过持续的迭代建成的。迭代来自不断地从事故中学习和持续改进的组织文化。

故障转移演练、计划下线、容量规划和黑盒测试

有一些定期活动需要投入大量的时间和精力,但对于保持大型分布式系统正常运行至关重要。这些是我在 Uber 首次接触的概念 —— 在过去的公司里,由于我们的规模和基础设施较小,我们不需要使用它们。

直到我观察了一些具体案例,我曾一直认为数据中心故障转移演练很乏味。我最初的想法是,设计健壮的分布式系统,就是指设计能够适应数据中心故障的系统。既然它理论上应该可用,为什么要定期测试?答案与系统的规模和为了测试服务是否能有效地处理新的数据中心的流量增长有关。

我观察到最常见的故障情况是,在发生故障转移时,服务在新数据中心没有足够的资源来应对全局流量。想象一下,服务 A 和服务 B 分别在两个数据中心运行。我们假设资源的利用率是 60% —— 每个数据中心分别运行成百上千个虚拟机 —— 并且警报的触发点设置在 70%。接着出现一个故障转移,导致所有的流量从数据中心 A 转移到了数据中心 B。在没有配置新机器的情况下,数据中心 B 无法应对这样的负载。配置新机器可能需要较长的时间,而请求可能积压并且中断。这种阻塞可能开始影响另外的服务,导致了其他系统的级联故障,尽管这些系统并不是这次故障转移的一部分。

数据中心故障转移中可能的出错方式

其它常见的故障情况可能涉及路由级问题,网络容量问题,或背压(back pressure)问题。任何可靠的分布式系统应该能够在不影响用户的情况下,执行数据中心故障转移的演练。我在强调应该 —— 这个演练对于测试分布式系统网络可靠性是非常有用的。

计划内的服务下线演练是测试整个系统弹性的最佳方法。它们对发现特定系统的隐藏的依赖关系或不合适/预期外的使用也很有帮助。虽然针对面向客户的和依赖少的服务,这个演练相对容易完成,但对于需要高可用的或依赖多的关键系统来说,这样做并不容易。然而,有一天这个关键系统不可用时,将会发生什么?相比于一次意料之外的中断,最好还是通过受控制的演练,在所有团队都清楚并准备就绪后,验证这个答案。

黑盒测试是一种在类似最终用户的条件下,测试系统正确性的方法。这种类型测试很像端到端测试。对于大多数产品而言,需要进行合适的黑盒测试。关键用户流程和最常见的面向用户的测试情景使“黑盒可被测试”:随时可以执行测试来检查系统是否正常工作。

以 Uber 为例,一个明显的黑盒测试是检查是否乘客-司机流在城市层面上正常工作。即某个特定城市的乘客是否能请求一辆 Uber 出租车,和司机匹配并上路?当这个情景变得自动化,这个测试就可以定期运行,并且模拟不同的城市。拥有健壮的黑盒测试,可以更容易地验证系统或系统的部件是否正常工作。它也对故障转移演练很有帮助:运行黑盒测试是在故障转移中获得反馈的最快方式。

在一次失败的故障转移演练中使用黑盒测试的例子,手动回滚数分钟。

容量规划对于大型分布式系统同样很重要。大型的意思是,计算和存储空间每个月将花费几万或几十万美元。在这种规模下,具有固定数量的部署可能比使用自扩展的云端解决方案更便宜。至少,固定部署可以应对“正常经营”的流量,并在峰值负载下自动扩展。但是,下个月最少应该运行多少实例?接下来的三个月呢?明年呢?

对于成熟且具有良好历史数据的系统来说,预测未来的流量模式并不困难。而且,这对于预算安排,选择云服务商或锁定云服务商的折扣都很重要。如果你的服务有着一大笔账单,而你还没有想过容量规划,你正错过一个简单的减少和控制成本的机会。

SLOs、SLAs 和报告它们

SLO 表示了服务质量目标 —— 一个系统可用性的数值目标。对于每个独立的服务,定义好服务级的 SLO,比如容量、延迟、准确度、可用度的目标,是一个很好的实践。这些 SLO 可以作为警报的触发器。一个服务级的 SLO 的例子如下所示:

SLO 指标 子类别 服务的目标值
容量 最小吞吐量 500 请求/秒
预期最大吞吐量 2,500 请求/秒
延迟 预期的响应时间中位数 50-90ms
预期的 p99 响应时间 500-800ms
准确度 最大错误率 0.5%
可用性 保证正常运行 99.9%

业务级的 SLO 或者功能性的 SLO 是在服务之上的抽象。它们将会涵盖面向用户或业务的指标。比如,一个业务级的 SLO 可以像这样:预计 99.99% 的电子邮件收据,会在行程结束后的一分钟内发出。这个 SLO 可能可以映射到服务级的 SLO (例如:支付和电子邮件收据系统的延迟);或者可能需要以别的方式测量。

SLA —— 服务质量协议是服务提供者和服务消费者之间更广泛的协议。通常,多个 SLO 组成了一个 SLA。例如,支付系统保持 99.99% 的可用性可以是一个 SLA,而它又可以分解为每个支撑系统具体的 SLO。

定义好 SLO 之后,下一步是测量这些目标并报告它们。自动监控和报告 SLA 和 SLO 通常是个复杂的项目,工程团队和业务团队会想要降低它的优先级。工程师团队不会太感兴趣,他们已经有了各种级别的监控来实时检测中断。而业务团队宁愿优先提供实用的功能,而不是把资源投入一项没有立竿见影的商业影响的复杂的工程中。

这将引出下个话题:运营大型分布式系统的组织,迟早需要专门的人员确保系统的可靠性。让我们聊聊网站可靠性工程团队。

SRE 作为一支独立团队

网站可靠性工程起源于 Google,从 2003 年左右开始 —— 至今 Google 已经有超过 1500 名 SRE 工程师。随着生产环境的运维变得越来越复杂,需要越来越多的自动化,这项工作将变成一种全职工作。这取决于公司何时认识到,工程师在生产自动化中几乎投入全职工作的时间:这些系统越重要、出现的故障越多,这种改变越早发生。

快速成长的科技公司通常在早期建立一支 SRE 团队,团队会制定自己的路线图。在 Uber,SRE 团队成立于 2015 年,它的使命是持续管理系统复杂性。其它公司可能在建立专门的基础设施团队时组建这样的团队。当一家公司发展到,稳定性工作消耗了团队中不少工程师的时间,就该建立这样一支专门的团队了。

SRE 团队让所有工程师都更加轻松地运维大型分布式系统。这支 SRE 团队很可能拥有标准的监控和警报工具。他们很可能购买或搭建值班待命工具,也是求助关于值班待命的最佳实践的首选。他们能促进事故回顾和系统构建,使检测、缓解和预防系统中断变得更加容易。他们肯定有助于故障转移演练,通常主导黑盒测试,并参与容量规划。他们推动了选择,改造或创建用于定义和测量 SLO 的标准工具,并报告它们。

鉴于不同的公司有不同的痛点需要 SRE 解决,SRE 的组织结构在各公司之间是不同的。它通常可能也有别的名字:它可能被称作运维,平台工程,或是基础架构。Google 免费发布了两本关于网站可靠性的必读书籍,这是深入了解 SRE 的最佳读物。

持续投资可靠性

在构建任何产品时,构建第一个版本仅仅是个开始。在第一版之后,将在迭代中添加新功能。如果产品成功并取得了商业上的回报,这些工作会不停地增加。

分布式系统有着类似的生命周期,然而它们需要更大的投入,不仅需要添加新功能,还得跟上规模的扩展。随着系统承受更高的负载,存储更多数据,需要更多的工程师为其工作,系统需要持续的维护才能保持平稳地运行。许多人第一次构建分布式系统时,把这个系统当作了一辆车:一旦造好,只需要每隔几个月进行必要的维护。这样类比其实不太对。

我喜欢把运维分布式系统看作运营一家大型机构,比如一家医院。为了确保医院运营良好,需要持续的验证和检查(监控、警报、黑盒测试)。新员工和设备(新工程师和新服务)都需要受到训练。随着工程师和服务的数量的增长,旧的方式变得低效:就像农村小诊所与城市大医院有着不同的运营方式。改进效率成了一项全职工作,测量和报告效率变得重要。就像大医院有更多的支持人员,比如财务、人资或安保;运维大型的分布式系统同样依赖于支持团队,比如基础设施和 SRE 团队。

为了使分布式系统可靠,组织机构需要持续在系统运维和系统的基础平台上进行更大的投入。

更多的推荐阅读

虽然这篇文章内容很长,但它仍仅仅略窥门径。如果要深入探索分布式系统的运维,我推荐的资源如下:

书籍:

在线资源:

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏