本文是 Uber 的工程师 Gergely Orosz 的文章,原文地址在:https://blog.pragmaticengineer.com/operating-a-high-scale-distributed-system/
在过去的几年里,我一直在构建和运营一个大型分布式系统:优步的支付系统。在此期间,我学到了很多关于分布式架构概念的知识,并亲眼目睹了高负载和高可用性系统运行的挑战(一个系统远远不是开发完了就完了,线上运行的挑战实际更大)。构建系统本身是一项有趣的工作。规划系统如何处理10x / 100x流量的增加,确保数据持久,面对硬件故障处理等等,这些都需要智慧。不管怎样,运维大型分布式系统对我来说是一次令人大开眼界的体验。
系统越大,墨菲的“什么可能出错,就会出错”的定律就越会体现。频繁部署、部署代码的开发人员数量很多,涉及多个数据中心、系统被大量全球用户使用,这种出错概率越大。在过去的几年里,我经历过各种各样的系统故障,其中很多让我感到惊讶。有些来自可预测的事情,比如硬件故障或一些看起来无害的Bug,还有数据中心线缆被挖断、同时发生多个级联故障。我经历了数十次业务停摆,系统的某些部分无法正常工作,从而导致巨大的业务影响。
这篇文章是我在Uber工作时总结的,可以有效运维大型系统的实践的集合。我的经验并不是独一无二的 - 在类似规模的系统上工作的人也经历了类似的旅程。我与Google,Facebook和Netflix的工程师进行了交谈,他们分享了类似的经验和解决方案。这里列出的许多想法和流程应该适用于类似规模的系统,无论是在自己的数据中心(如Uber大多数情况下)上运行,还是在云上运行(Uber 有时会把部分服务弹性部署到云上)。但是,对于规模较小或较少关键任务的系统而言,这些做法可能过于苛刻。
涉及的内容很多——我将讨论以下主题:
- 监控
- 值班,异常检测和警报
- 故障和事件管理流程
- 事后分析,事件回顾和持续改进文化
- 故障演习,容量规划和黑盒测试
- SLOs、SLAs 及其报告
- SRE 作为独立团队
- 可靠性作为持续投入
- 更多推荐阅读
监控
要知道系统是否健康,我们需要回答“我的系统是否正常工作”的问题?为此,收集系统关键部分的数据至关重要。对于在多台计算机和数据中心上运行多个服务的分布式系统,可能很难确定要监控的关键内容是什么。
基础设施健康监测 如果一个或多个计算机/虚拟机过载,则分布式系统的某些部分可能会降级。机器的健康状况,CPU利用率、内存使用情况,是值得监控的基础内容。有些平台可以开箱即用地处理这种监控和自动扩展实例。在优步,我们拥有一支优秀的核心基础设施团队,提供开箱即用的基础设施监控和警报。不管技术层面如何实现,实例或基础设施出问题的时候,监控平台需要提供必要的信息。
服务运行状况监控:流量,错误,延迟。我们经常需要回答“这个后端服务是否健康?”这样的问题。观察访问端点的请求流量、错误率和端点延迟等事项都可以提供有关服务健康状况的有价值信息。我更喜欢将这些都显示在仪表板上。在构建新服务时,通过使用正确的HTTP响应映射并监视相关代码可以对系统有很多了解。因此,确保客户端错误能返回4XX,以及如果服务器错误则返回5xx,这种监控易于构建且易于解释。
监测延迟值得再考虑一下。对于生产服务,目标是让大多数最终用户获得良好的体验。事实证明,测量平均延迟并不是一个很好的指标,因为这个平均值可以隐藏一小部分高延迟请求。测量p95,p99或p999 - 第95百分位,第99百分位或第99.9百分位的请求所经历的延迟 - 是一个更好的指标。这些数字有助于回答诸如“99%的人的请求有多快?”之类的问题(p99)。或“1000人中,至少有一人经历了多慢的延迟?” (p999)。对于那些对这个主题更感兴趣的人,这篇延迟入门文章可以进一步阅读。
从图上可以明显看出,平均延迟、p95、p99 差异还是比较大的。所以平均延迟有可能掩盖一些问题。
围绕监控和可观察性有很多更有深度的内容。值得一读的两个资源是Google的SRE书和关于分布式系统监控的四个黄金指标的部分。他们建议,如果您只能测量面向用户的系统的四个指标,请关注流量,错误,延迟和饱和度。比较简短的材料的话,推荐来自Cindy Sridharan的分布式系统可观察性电子书,它涉及其他有用的工具,如事件日志,指标和跟踪最佳实践。
业务指标监控。监控服务模块,可以告诉我们服务模块运行的如何如何正常,但无法告知我们业务是否按预期工作,是否“照常营业”。在支付系统,一个关键问题是,“人们可以使用特定的支付方式进行支付业务吗?”。识别业务事件并对其监控,是最重要的监控步骤之一。
虽然我们建立了各种监控,有些业务问题仍然无法探测到,这让我们遭受了巨大的痛苦,最终建立了业务指标的监控。有时我们所有的服务看起来都在正常运行,但关键产品功能不可用!这种监控对我们的组织和领域来说非常有用。因此,我们必须在Uber的可观察性技术堆栈的基础上,为自己定制这种类型的监控做了大量的思考和努力。
译者注:业务指标监控这一点,我们实在是太深有同感了,之前在滴滴有时就是发现所有服务都正常,但是业务不好使。我们现在创业做的北极星系统,就是专门应对这个问题的。感兴趣的朋友可以在公众号后台给我留言,或加我好友 picobyte 交流试用。
Oncall,异常检测和警报
监控对于洞察系统的当前状态而言,是一个很棒的工具。但这只是自动检测问题并发出警报以供人们采取行动的一个垫脚石。
Oncall 本身是一个广泛的话题 - Increment 杂志在其 “On-Call 问题”中涵盖了许多方面的内容。我的强烈认为,如果你拥有了"you build it, you own it"的心态,那随着而来的就是 OnCall。构建服务的团队拥有这些服务,也负责值班。我们的团队负责支付服务的值班。因此,每当出现警报时,值班工程师都会响应并查看详细信息。但是如何从监控到警报呢?
从监控数据中检测异常是一个艰巨的挑战,也是机器学习可以发光的领域。有很多第三方服务提供异常检测。再次幸运的是,我们团队有一个内部机器学习团队与之合作,他们根据Uber的使用情况量身定制了解决方案。位于纽约的 Observability 团队撰写了一篇有用的文章,介绍 Uber 的异常检测工作原理。从我的团队的角度来看,我们将监控数据推送到该团队的管道,并获得具有各自置信度的警报。然后我们决定是否应该呼叫工程师。
何时触发警报是一个有趣的问题。警报太少可能意味着错过有影响的中断。太多会导致不眠之夜并使人筋疲力尽。跟踪和分类警报以及测量信噪比对于调整警报系统至关重要。检查警报并标记它们是否可操作,然后采取措施减少不可操作的警报,这是朝着实现可持续的随叫随到轮换迈出的良好一步。
Uber 使用的内部 oncall 仪表板示例,由 Vilnius 的 Uber 开发人员体验团队构建。
位于 Vilnius 的Uber开发工具团队构建了整洁的呼叫工具,我们用它来注释警报并可视化呼叫班次。我们的团队每周对上一次值班班次进行回顾,分析痛点并花时间改善值班体验,一周又一周。
译者注:告警事件的聚合、降噪、排班、认领、升级、协同、灵活的推送策略、多渠道推送、和IM打通,是很通用的需求,可以参考 FlashDuty 这个产品,体验地址:https://console.flashcat.cloud/
故障和事件管理流程
想象一下:你是本周的值班工程师。半夜,一个警报把你吵醒了。你调查是否有生产中断发生。糟糕,似乎系统的某个部分出现了故障。现在怎么办?监控和警报真实发生了。
对于小型系统来说,中断可能不是什么大问题,值班工程师可以了解正在发生的事情以及原因。它们通常易于理解且易于缓解。对于具有多个(微)服务的复杂系统,许多工程师将代码推向生产,仅仅查明潜在问题发生的位置就已经足够具有挑战性了。有一些标准流程来帮助解决这个问题会产生巨大的改观。
附加到警报的Runbook手册,描述简单的缓解步骤是第一道防线。对于拥有良好Runbook手册的团队,即使值班工程师不深入了解系统,也很少会成为问题。Runbook 需要保持最新、更新,并在故障出现时使用新型缓解措施进行处理。
译者注:Nightingale 和 Grafana 的告警规则配置中,可以支持自定义字段,但是有些附加字段是默认就会提供的,比如 RunbookUrl,核心就是想传达SOP手册的重要性。另外,稳定性治理体系里,告警规则是否预置了RunbookUrl,是一个很重要的告警健康度的衡量指标。
一旦有超过几个部署服务的团队,跨组织进行故障交流就变得至关重要。在我工作的环境中,成千上万的工程师会根据自己的判断将他们所开发的服务部署到生产环境中,每小时可能会有数百次部署。一个看似不相关的服务部署可能会影响另一个服务。在这种情况下,标准化的故障广播和通信渠道可以起到很大作用。我曾经遇到过多种罕见的警报信息 - 意识到其他团队中的人也看到了类似奇怪现象。通过加入一个集中式聊天群组来处理故障,我们迅速确定了导致故障的服务并解决了问题。我们做得比任何单独一人更快地完成了任务。
现在缓解,明天调查。在故障期间,我经常会感到“肾上腺素飙升”,想要修复出现问题的地方。通常根本原因是糟糕的代码部署,在代码更改中存在明显的错误。过去,我会立即跳进去修复错误、推送修复并关闭故障,而不是回滚代码更改。然而,在故障期间修复根本原因是一个可怕的想法。采用前进式修复收益甚微,损失却很大。因为新的修复需要迅速完成,所以必须在生产中进行测试。这就是引入第二个错误 - 或者在现有错误之上再出现一个故障 - 的原因。我见过像这样的故障不断恶化。只需先集中精力缓解,抵制修复或调查根本原因的冲动。适当的调查可以等到下一个工作日。
译者注:这一点老司机应该也深有感触,不要在线上Debug,出现问题立即回滚而不是尝试发布hotfix版本来修复!
事后分析,事件回顾和持续改进文化
这是在说一个团队如何处理故障的后续。他们会继续工作吗?他们会做小规模的调查吗?他们是否会在后续工作中花费惊人的精力,停止产品工作以进行系统级修复?
正确进行的事后分析是构建强大系统的基石。一份好的事后分析既无指责,又十分彻底。Uber 的事后分析模板随着工程技术的发展而不断演变,包括事件概述、影响总览、时间线、根本原因分析、经验教训以及详细跟进清单等部分。
这是一个类似我在 Uber 工作中用到的复盘模板。
良好的事后分析深入挖掘根本原因并提出改进措施,以更快地预防、检测或缓解所有类似的故障。当我说深入挖掘时,我的意思是他们不会停留在根本原因上,即错误的代码更改和代码审查者没有发现错误。
他们使用“5why”探索方式进行更深入的挖掘,以达到更有意义的结论。举个例子:
- 为什么会出现这个问题?–> 因为代码里引入了bug。
- 为什么其他人没有发现这个错误?–> 代码审查员没有注意到代码更改可能会导致此类问题。
- 我们为什么只依赖于代码审查员捕获此错误?–> 因为我们没有针对此用例的自动化测试。
- “为什么我们没有针对此用例的自动化测试?” –> 因为在没有测试帐户的情况下很难进行测试。
- 我们为什么没有测试帐户? –> 因为该系统尚不支持它们
- 结论:这个问题指向了缺乏测试账户的系统性问题。建议将测试账户支持添加到系统中。接下来,编写所有未来类似代码更改的自动化测试。
事件回顾是事后分析的重要配套工具。虽然许多团队在事后分析方面做得很彻底,但其他团队可以从额外的输入和对预防性改进的挑战中受益。同样重要的是,团队要有责任感并有权执行他们提出的系统级改进。
对于认真对待可靠性的组织,最严重的故障会由经验丰富的工程师进行审查和挑战。组织级别的工程管理人员也应该出席,以提供授权来完成修复——尤其是当这些修复很耗时并阻碍其他工作时。健壮的系统不是一蹴而就的:它们是通过不断的迭代构建的。怎么才能持续迭代?这需要组织层面有持续改进、从故障中学习的文化。
故障演习,容量规划和黑盒测试
有一些常规活动需要大量投资,但对于保持大型分布式系统的正常运行至关重要。这些是我在优步第一次接触到的概念——在以前的公司,我们不需要使用这些,因为我们的规模和基础设施没有促使我们这样做。
一个数据中心故障演练是我认为很无聊的事情,直到我观察了其中几个实践。我最初的想法是,设计强大的分布式系统正是为了能够在数据中心崩溃时保持弹性。如果理论上它可以正常工作,为什么要经常测试呢?答案与规模有关,并且需要测试服务是否能够有效地处理新数据中心中突然增加的流量。
我观察到的最常见的故障场景是在发生故障转移时,新数据中心的服务没有足够的资源来处理全球流量。假设ServiceA和ServiceB分别从两个数据中心运行。假设资源利用率为60%,每个数据中心都有数十或数百台虚拟机在运行,并设置警报以在70%时触发。现在让我们进行故障转移,将所有流量从DataCenterA重定向到DataCenterB。 在没有提供新机器的情况下,DataCenterB突然无法承受负载。提供新机器可能需要足够长的时间,以至于请求会堆积并开始丢弃。这种阻塞可能会开始影响其他服务,导致其他系统的级联故障,这些系统甚至不是此故障转移的一部分。
其他常见的故障场景包括路由级别问题、网络容量问题或背压痛点。数据中心故障转移是任何可靠分布式系统应该能够在没有任何用户影响的情况下执行的演习。我强调“应该”——这个演习是测试分布式系统可靠性最有用的练习之一。
译者注:切流量,本就是预案“三板斧”之一。出故障的时候,要保证预案是可用的,那平时就少不了演练。重视起来吧,老铁们。
计划的服务停机时间练习是测试整个系统弹性的极好方法。这些也是发现特定系统的隐藏依赖项或不适当/意外使用的好方法。虽然对于面向客户且依赖较少的服务,这种练习相对容易完成,但是对于需要高可用性或被许多其他系统所依赖的关键系统来说,则不那么容易喽。但是,当某一天这个关键系统不可用时会发生什么?最好通过受控演练来验证答案,所有团队都知道并准备好应对意外中断。
黑盒测试是一种测量系统正确性的方法,尽可能接近最终用户所看到的条件。这种类型的测试类似于端到端测试,但对于大多数产品来说,拥有适当的黑盒测试需要单独投入。关键用户流程和最常见的面向用户的测试场景是好的黑盒可测性示例:以这种方式进行设置可以随时触发它们,以检查系统是否正常工作。
以优步为例,一个明显的黑盒测试是检查乘客-司机流程是否在城市层面上正常工作。也就是说,在特定城市内的乘客能否请求优步,并与司机合作并完成行程?一旦这种情况被自动化,这个测试可以定期运行,模拟不同的城市。拥有强大的黑盒测试系统使得验证系统或部分系统是否正确工作更加容易。它还对故障转移演练非常有帮助:获取故障转移反馈最快捷的方法是运行黑盒测试。
上图是在故障转移演练失败时,使用黑盒测试的示例,在演练几分钟后手动回滚。
容量规划对于大型分布式系统同样重要。所谓大型,是指计算和存储成本每月达到数万或数十万美元。在这个规模下,使用固定数量的部署可能比使用自动扩展的云解决方案更便宜。至少,固定部署应该处理“业务常态”流量,并在高峰负载时进行自动扩展。但是,在接下来的一个月内、未来三个月内以及明年需要运行多少最小实例呢?
预测成熟且具有良好历史数据的系统的未来流量模式并不困难。这对于预算、选择供应商或锁定云提供商的折扣都很重要。如果您的服务费用很高,而您没有考虑容量规划,那么您就错过了降低和控制成本的简单方法。
SLOs, SLAs 以及相关报告
SLO 代表服务级别目标 - 系统可用性的数字目标。对于每个独立的服务,定义服务级别 SLO(例如容量、延迟、准确性和可用性的目标)是一种很好的做法。然后,这些 SLO 可以作为警报的触发器。服务级别 SLO 示例可能如下所示:
SLO Metric | Subcategory | Value for Service |
Capacity | Minumum throughput | 500 req/sec |
Maximum expected throughput | 2,500 req/sec | |
Latency | Expected median response time | 50-90ms |
Expected p99 response time | 500-800ms | |
Accuracy | Maximum error rate | 0.5% |
Availability | Guaranteed uptime | 99.9% |
业务级 SLO 或功能 SLO 是服务之上的抽象。它们将涵盖用户或面向业务的指标。例如,业务级 SLO 可能是这样的:期望 99.99% 的电子邮件收据在旅行完成后的 1 分钟内发送。此 SLO 可能映射到服务级别 SLO(例如支付和电子邮件接收系统的延迟),或者它们可能需要以不同方式衡量。
SLA - 服务水平协议 - 是服务提供者和服务消费者之间更广泛的协议。通常,多个 SLO 组成一个 SLA。例如,99.99% 可用的支付系统可以是 SLA,它分解为每个支持系统的特定 SLO。
定义 SLO 后,下一步是衡量这些并报告它们。对 SLA 和 SLO 进行自动化监控和报告通常是一项复杂的项目,工程团队和业务部门都倾向于降低其优先级。工程团队可能不太感兴趣,因为他们已经有各种级别的监控来实时检测故障。另一方面,业务部门更愿意将重点放在提供功能上,而不是投资于一个没有立即商业影响的复杂项目中。
这就引出了下一个话题:运营大型分布式系统的组织迟早需要为这些系统的可靠性投入专门的人员。让我们来谈谈网站可靠性工程团队。
SRE 作为独立团队
网站可靠性工程(Site Reliability Engineering)起源于谷歌,大约在2003年左右开始,现在已经发展成为拥有超过1,500名SRE工程师的团队。随着生产环境运营变得越来越复杂,并需要更多自动化操作,这项工作很快就会成为一项全职工作。公司意识到他们有工程师正在全职从事生产自动化的时间因情况而异:这些系统越关键、故障越多,则此类情况出现得越早。
快速发展的科技公司通常会在早期组建 SRE 团队,由他们制定自己的路线图。在优步,SRE 团队成立于 2015 年,其使命是随着时间的推移管理系统复杂性。其他公司可能会在创建专门的基础架构团队时同时启动这样的团队。当一家公司发展到跨团队的可靠性工作占用了很多工程师的时间时,是时候为此设立一个专门的团队了。
有了 SRE 团队,这个团队可以让所有工程师更轻松地维护大型分布式系统的操作方面。SRE 团队可能拥有标准的监控和警报工具。他们可能购买或构建 oncall 工具,并且是 oncall 最佳实践的 goto 团队。他们可能会促进故障复盘并构建系统,以更轻松地检测、缓解和防止故障。他们当然有助于故障转移演练,经常推动黑盒测试,并参与容量规划。他们推动选择、定制或构建标准工具来定义和衡量 SLO 并报告它们。
鉴于公司有不同的痛点,他们寻求 SRE 来解决,因此 SRE 组织在公司之间是不同的。这个团队的名称通常也会不同:可能被称为运维、平台工程或基础设施。Google 出版了两本关于站点可靠性的必读书籍,这些书籍可免费获取,是深入了解 SRE 的绝佳读物。
可靠性作为持续投入
在构建任何产品时,构建第一个版本只是一个开始。在 v1 之后,新功能会添加到即将到来的迭代中。如果产品成功并带来业务成果,那么工作就会继续进行。
分布式系统具有相似的生命周期,只是它们需要更多投资,不仅仅是为了新功能,还要跟上扩展的步伐。随着系统开始承受更多负载、存储更多数据、更多工程师对其进行处理,它需要持续关注以保持平稳运行。很多人第一次构建分布式系统时会认为这个系统就像一辆汽车:一旦建好,只需要每几个月进行必要的维护。但是这种比较与实际情况相去甚远。
我喜欢把操作分布式系统的努力比作经营大型组织,例如医院。为了确保医院运转良好,需要进行持续的验证和检查(监控、警报、黑盒测试)。不断有新员工和设备加入:对于医院来说,这是像护士和医生这样的员工以及新的医疗设备;对于分布式系统来说,则是招募新工程师和添加新服务。随着人数和服务数量的增长,旧有做事方式变得低效:就像乡村小诊所与大都市中的大型医院不同一样。提出更有效率的方法成为全职工作,并且测量并报告效率变得重要。就像大型医院有财务、人力资源或安全等支持性质的办公室人员一样,经营较大规模分布式系统也依赖基础架构和SRE等支持团队。
为了让团队运行可靠的分布式系统,组织需要持续投资于这些系统的操作以及它们所构建的平台。