正如站点可靠性工程:Google 如何运营生产系统1 (以下简称 SRE 书)中详细介绍的那样,Google 产品和服务在追求高速功能开发的同时,还要保持对可用性和响应速度的严格 SLO(服务级别目标)。SLO 表示服务应该几乎总是处于运行状态,并且服务应该几乎总是很快;SLO 还提供了精确的数字来定义特定服务的“几乎总是”意味着什么。SLO 基于以下观察:
绝大多数软件服务和系统应该追求接近完美的可靠性,而不是完美的可靠性——即 99.999% 或 99.99%,而不是 100%——因为用户无法区分服务是 100% 可用还是低于“完美”可用。用户和服务之间路径上还有许多其他系统(笔记本电脑、家庭 WiFi、ISP、电网等),这些系统加起来的可用性远低于 100%。因此,99.99% 和 100% 之间的微小差异会淹没在其他不可用性的噪音中,用户无法从为增加最后那百分之几的可用性所需付出的巨大努力中获得任何好处。此规则的显著例外包括防抱死制动控制系统和起搏器!
有关 SLO 如何与 SLI(服务级别指标)和 SLA(服务级别协议)相关的详细讨论,请参阅 SRE 书中的“服务级别目标”章节。该章节还详细介绍了如何为特定服务或系统选择有意义的指标,这反过来又驱动了为该服务选择合适的 SLO。
本文扩展了 SLO 的主题,重点关注服务依赖项。具体而言,我们研究关键依赖项的可用性如何影响服务的可用性,以及如何进行设计以减轻和最小化关键依赖项。
Google 提供的大多数服务都旨在为用户提供 99.99%(有时称为“四个 9”)的可用性。一些服务在合同上承诺较低的外部数字,但在内部设定 99.99% 的目标。这个更严格的目标考虑了用户在合同违规发生之前很久就对服务性能不满意的 ситуации,因为 SRE 团队的首要目标是让用户满意。对于许多服务来说,99.99% 的内部目标代表了成本、复杂性和可用性之间的最佳平衡点。对于某些服务,特别是全球云服务,内部目标是 99.999%。
让我们研究一下关于设计和运营 99.99% 服务的几个关键观察和启示,然后转向实际应用。
中断主要来自两个来源:服务本身的问题以及服务关键依赖项的问题。关键依赖项是指如果发生故障,会导致服务中发生相应故障的依赖项。
可用性是中断频率和持续时间的函数。它通过以下方式衡量:
* 中断频率,或其倒数:MTTF(平均失效时间)。
* 持续时间,使用 MTTR(平均修复时间)。持续时间定义为用户体验到的时间:从故障开始到正常行为恢复的时间。
因此,可用性在数学上定义为 MTTF/(MTTF+MTTR),使用适当的单位。
服务的可用性不可能高于其所有关键依赖项的交集。如果你的服务旨在提供 99.99% 的可用性,那么你的所有关键依赖项的可用性都必须远高于 99.99%。
在 Google 内部,我们使用以下经验法则:关键依赖项必须比你的服务多提供一个 9 的可用性——在示例案例中,为 99.999% 的可用性——因为任何服务都将有多个关键依赖项,以及其自身的特殊问题。这称为“额外 9 的规则”。
如果你有一个关键依赖项没有提供足够的 9(一个相对常见的挑战!),你必须采取缓解措施来提高你的依赖项的有效可用性(例如,通过容量缓存、故障打开、在面对错误时优雅降级等等。)
服务的可用性不可能高于其事件频率乘以其检测和恢复时间。例如,每年三次完整中断,每次持续 20 分钟,总共导致 60 分钟的中断。即使服务在一年中的其余时间完美运行,99.99% 的可用性(每年停机时间不超过 53 分钟)也是不可行的。
这个启示只是数学,但它经常被忽视,并且可能非常不方便。
如果你的服务被依赖以达到你无法交付的可用性级别,你应该努力纠正这种情况——通过提高你的服务的可用性级别或添加如前所述的缓解措施。降低期望(即,发布的可用性)也是一种选择,而且通常是正确的选择:向依赖服务明确表示,它应该重新设计其系统以补偿你的服务的可用性,或者降低其自身的目标。如果你不纠正或解决这种差异,中断将不可避免地迫使需要纠正它。
让我们考虑一个目标可用性为 99.99% 的示例服务,并研究其依赖项和中断响应的要求。
假设你的 99.99% 可用服务具有以下特征:
* 每年一次重大中断和三次次要中断。请注意,这些数字听起来很高,但 99.99% 的可用性目标意味着每年有 20 到 30 分钟的广泛中断和几次短暂的部分中断。(数学计算做了两个假设:从 SLO 的角度来看,单个分片的故障不被视为整个系统的故障,并且总体可用性是使用区域/分片可用性的加权总和计算的。)
* 对其他独立的 99.999% 服务有五个关键依赖项。
* 五个独立的分片,它们之间不能相互故障转移。
* 所有更改都以渐进方式推出,一次一个分片。
可用性计算如下。
依赖项要求
* 全年中断的总预算为 525,600 分钟/年的 0.01%,即 53 分钟(基于 365 天的一年,这是最坏情况)。
* 分配给关键依赖项中断的预算是五个独立的关键依赖项,每个依赖项的预算为 0.001% = 0.005%;525,600 分钟/年的 0.005%,即 26 分钟。
* 考虑到关键依赖项的中断,由你的服务引起的中断的剩余预算为 53 - 26 = 27 分钟。
中断响应要求
* 预计中断次数:4 次(1 次完全中断,3 次仅影响单个分片的中断)
* 预计中断的总体影响:(1 x 100%)+(3 x 20%)= 1.6
* 检测和从中断中恢复的可用时间:27/1.6 = 17 分钟
* 分配给检测和警报中断的监控时间:2 分钟
* 分配给随叫随到响应者开始调查警报的时间:5 分钟。(随叫随到意味着技术人员携带一个寻呼机,当服务发生中断时,该寻呼机会收到警报,这基于跟踪和报告 SLO 违规的监控系统。许多 Google 服务由 SRE 随叫随到轮班支持,以处理紧急问题。)
* 有效缓解的剩余时间:10 分钟
值得仔细研究一下刚刚提出的数字,因为它们突出了一个基本点:有三个主要的杠杆可以使服务更可靠。
* 减少中断频率——通过推出策略、测试、设计评审等。
* 减少平均中断的范围——通过分片、地理隔离、优雅降级或客户隔离。
* 缩短恢复时间——通过监控、一键式安全操作(例如,回滚或增加紧急容量)、运营就绪实践等。
你可以在这三个杠杆之间进行权衡,以使实施更容易。例如,如果 17 分钟的 MTTR 难以实现,那么请将精力集中在减少平均中断的范围上。本文稍后将更深入地讨论最小化和缓解关键依赖项的策略。
一位随意的读者可能会推断,依赖链中的每个附加链接都需要一个额外的 9,这样二阶依赖项需要两个额外的 9,三阶依赖项需要三个额外的 9,依此类推。
这种推论是不正确的。它基于将依赖层次结构建模为在每个级别具有恒定扇出的树的朴素模型。在这样的模型中,如图 1 所示,有 10 个唯一的一阶依赖项,100 个唯一的二阶依赖项,1,000 个唯一的三阶依赖项,依此类推,即使架构仅限于四层,也会导致总共 1,111 个唯一服务。具有如此多独立关键依赖项的高度可用服务生态系统显然是不现实的。
无论关键依赖项 X 出现在依赖树中的哪个位置,它本身都可能导致整个服务(或服务分片)的故障。因此,如果给定的组件 X 作为服务的几个一阶依赖项的依赖项出现,则 X 应该只计数一次,因为无论有多少中间服务也受到影响,它的故障最终都会导致服务失败。
正确的规则如下:
* 如果一个服务有 N 个唯一的关键依赖项,那么每个依赖项都对顶级服务的依赖项引起的不可用性贡献 1/N,而与其在依赖层次结构中的深度无关。
* 每个依赖项应仅计数一次,即使它在依赖层次结构中多次出现(换句话说,仅计数唯一的依赖项)。例如,在计算图 2 中服务 A 的依赖项时,服务 B 在总数 N 中仅计数一次。
例如,考虑一个假设的服务 A,它的错误预算为 0.01%。服务所有者愿意将一半的预算用于他们自己的错误和损失,另一半用于关键依赖项。如果服务有 N 个这样的依赖项,则每个依赖项获得剩余错误预算的 1/N。典型的服务通常有大约 5 到 10 个关键依赖项,因此每个依赖项的故障频率只能是服务 A 的十分之一或二十分之一。因此,根据经验,服务的关键依赖项必须具有额外一个 9 的可用性。
SRE 书1 相当全面地介绍了错误预算的概念,但在此处值得一提。Google SRE 使用错误预算来平衡可靠性和创新步伐。此预算定义了服务在一段时间内(通常是一个月)可接受的故障级别。错误预算只是 1 减去服务的 SLO,因此之前讨论的 99.99% 可用服务具有 0.01% 的“预算”用于不可用性。只要服务没有用完本月的错误预算,开发团队就可以自由地(在合理的范围内)发布新功能、更新等等。
如果错误预算用完了,服务将冻结更改(紧急安全修复和解决最初导致违规的原因的更改除外),直到服务在预算中赚回空间,或者月份重置。Google 的许多服务都为 SLO 使用滑动窗口,因此错误预算会逐渐恢复。对于 SLO 大于 99.99% 的成熟服务,季度而不是每月预算重置是合适的,因为允许的停机时间量很小。
错误预算消除了 SRE 团队和产品开发团队之间可能出现的结构性紧张关系,方法是为他们提供一个通用的、数据驱动的机制来评估发布风险。它们还为 SRE 团队和产品开发团队提供了一个共同的目标,即开发实践和技术,以实现更快的创新和更多发布,而不会“超出预算”。
到目前为止,本文已经确立了可以称为“组件可靠性黄金法则”的内容。这仅仅意味着任何关键组件的可靠性必须是整个系统目标可靠性的 10 倍,以便其对系统不可靠性的贡献只是噪音。由此可见,在理想的世界中,目标是使尽可能多的组件成为非关键组件。这样做意味着组件可以遵守较低的可靠性标准,从而获得创新和承担风险的自由。
减少关键依赖项最基本和最明显的策略是尽可能消除 SPOF(单点故障)。更大的系统应该能够在没有非关键依赖项或 SPOF 的任何给定组件的情况下可接受地运行。
实际上,你可能无法摆脱所有关键依赖项,但你可以遵循一些关于系统设计的最佳实践来优化可靠性。虽然这样做并非总是可行,但如果在设计和规划阶段就计划可靠性,而不是在系统上线并影响实际用户之后,则更容易且更有效地实现系统可靠性。
当你在考虑新的系统或服务,或者重构或改进现有系统或服务时,架构或设计评审可以识别共享基础设施以及内部与外部依赖项。
共享基础设施
如果你的服务正在使用共享基础设施——例如,多个用户可见的产品使用的底层数据库服务——请考虑是否正确使用了该基础设施。明确识别共享基础设施的所有者作为额外的利益相关者。此外,注意不要使你的依赖项过载——与这些依赖项的所有者仔细协调发布。
内部与外部依赖项
有时,产品或服务依赖于公司控制范围之外的因素——例如,代码库,或者第三方提供的服务或数据。识别这些因素可以让你减轻它们带来的不可预测性。
在设计系统时,请牢记以下原则。
冗余和隔离
你可以通过将依赖项设计为具有多个独立实例来减轻你对关键依赖项的依赖。例如,如果在一个实例中存储数据为该数据提供 99.9% 的可用性,那么在三个广泛分布的实例中存储三个副本将提供 1 - 0.013 的理论可用性级别,或者九个 9,如果实例故障是独立的且零相关性。
在现实世界中,相关性永远不会为零(考虑同时影响多个单元的网络骨干网故障),因此实际可用性将远低于九个 9,但远高于三个 9。另请注意,如果系统或服务是“广泛分布的”,则地理分离并不总是非相关故障的良好代理。你可能最好在附近的多个位置使用多个系统,而不是在遥远的位置使用相同的系统。
同样,向一个集群中的一个服务器池发送 RPC(远程过程调用)可能会为结果提供 99.9% 的可用性,但是向三个不同的服务器池发送三个并发 RPC 并接受第一个到达的响应有助于将可用性提高到远高于三个 9(见上文)。如果服务器池与 RPC 发送者大致等距,则此策略还可以减少尾部延迟。(由于并发发送三个 RPC 的成本很高,因此 Google 通常会战略性地安排这些调用的时间:我们的大多数系统会在等待分配时间的一小部分后发送第二个 RPC,并在稍后发送第三个 RPC。)
故障转移和回退
追求故障安全的软件推出和迁移,并在出现问题时自动隔离。这里工作的基本原则是,当你让一个人上线以触发故障转移时,你可能已经超过了你的错误预算。
在并发/投票不可能的情况下,自动化故障转移和回退。同样,如果问题需要人来检查问题所在,那么满足你的 SLO 的机会很渺茫。
异步性
尽可能将依赖项设计为异步而不是同步,这样它们就不会意外地变成关键依赖项。如果服务等待来自其非关键依赖项之一的 RPC 响应,并且此依赖项的延迟出现峰值,则该峰值将不必要地损害父服务的延迟。通过使对非关键依赖项的 RPC 调用异步,你可以将父服务的延迟与依赖项的延迟解耦。虽然异步性可能会使代码和基础设施复杂化,但这种权衡是值得的。
容量规划
确保正确配置每个依赖项。如有疑问,如果成本可以接受,则过度配置。
配置
尽可能标准化你的依赖项的配置,以限制子系统之间的不一致性并避免一次性故障/错误模式。
检测和故障排除
使检测、故障排除和诊断问题尽可能简单。有效的监控是及时检测问题的关键组成部分。诊断具有深度嵌套依赖项的系统很困难。始终有一个缓解故障的答案,不需要操作员深入调查。
快速可靠的回滚
将人引入缓解计划会大大增加错过严格 SLO 的风险。构建易于、快速且可靠地回滚的系统。随着你的系统成熟,并且你对监控检测问题的能力充满信心,你可以通过工程化系统以自动触发安全回滚来降低 MTTR。
检查每个组件和依赖项,并确定其故障的影响。问自己以下问题:
* 如果服务的依赖项之一发生故障,服务是否可以继续在降级模式下提供服务?换句话说,为优雅降级而设计。
* 在不同情况下,你如何处理依赖项的不可用性?在服务启动时?在运行时?
设计和实施强大的测试环境,以确保每个依赖项都有自己的测试覆盖率,并且测试专门针对环境的其他部分期望的用例。以下是一些针对此类测试的推荐策略:
* 使用集成测试执行故障注入——验证你的系统可以在其任何依赖项发生故障时幸存下来。
* 进行灾难测试以识别弱点或隐藏/意外的依赖项。记录后续行动以纠正你发现的缺陷。
* 不要只进行负载测试。故意使你的系统过载,以查看其如何降级。无论如何,你的系统对过载的响应将会被测试;最好自己执行这些测试,而不是将负载测试留给你的用户。
预期随着规模的扩大而来的变化:最初作为单台机器上相对简单的二进制文件的服务可能会发展到在更大规模部署时具有许多明显的和不明显的依赖项。规模每扩大一个数量级,就会暴露出新的瓶颈——不仅对你的服务而言,而且对你的依赖项而言也是如此。考虑如果你的依赖项无法像你需要的那么快地扩展会发生什么。
还要注意,系统依赖项会随着时间的推移而演变,并且你的依赖项列表很可能会随着时间的推移而增长。在基础设施方面,Google 的典型设计指南是构建一个系统,该系统将在没有重大设计更改的情况下扩展到初始目标负载的 10 倍。
虽然读者可能熟悉本文涵盖的某些或许多概念,但将这些信息汇集在一起并以具体的术语表达可能会使这些概念更容易理解和教授。它的建议令人感到不安,但并非无法实现。许多 Google 服务一直持续交付优于四个 9 的可用性,这不是通过超人的努力或智慧,而是通过彻底应用多年来收集和改进的原则和最佳实践(请参阅 SRE 书的附录 B:生产服务的最佳实践汇编)。
定义
对于不专门从事运营的读者来说,本文中使用的某些术语和概念可能并不熟悉。
容量缓存:一种缓存,用于为 API 调用或服务查询提供预先计算的结果,通过减少客户端流量访问底层服务的量,从而节省计算/IO 资源需求方面的成本。
与更典型的性能/延迟缓存不同,容量缓存被认为是服务运营的关键。缓存命中率或缓存比率低于 SLO 被视为容量损失。一些容量缓存甚至可能牺牲性能(例如,重定向到远程站点)或新鲜度(例如,CDN),以便满足命中率 SLO。
客户隔离:将客户彼此隔离可能是有利的,这样一个客户的行为就不会影响其他客户。例如,你可以根据客户的全球流量将客户彼此隔离。当给定客户发送的流量激增超出其配置的流量时,你可以开始限制或拒绝此过量流量,而不会影响来自其他客户的流量。
故障安全/故障打开/故障关闭:优雅地容忍依赖项故障的策略。“安全”策略取决于上下文:在某些情况下,故障打开可能是安全策略,而在另一些情况下,故障关闭可能是安全策略。
故障打开:当通常需要授权操作的触发器失败时,故障打开意味着让某些操作发生,而不是做出决定。例如,通常需要徽章验证的建筑物出口门“故障打开”,以便你在断电期间无需验证即可离开。
故障关闭与故障打开相反。例如,如果银行金库门的徽章读取器无法联系访问控制数据库,则会拒绝所有解锁尝试。
故障安全意味着防止系统在预期功能突然无法工作时进入不安全模式所需的任何行为。例如,给定的系统可能能够通过提供缓存数据故障打开一段时间,然后在数据变得陈旧时故障关闭(可能是因为超过某个点,数据不再有用)。
故障转移:一种处理系统组件或服务实例故障的策略,方法是将传入请求自动路由到不同的实例。例如,你可以将数据库查询路由到副本数据库,或将服务请求路由到另一个数据中心的复制服务器池。
回退:一种机制,允许工具或系统在给定组件不可用时使用替代源来提供结果。例如,系统可能会回退到使用先前结果的内存缓存。虽然结果可能略有陈旧,但这种行为比彻底失败要好。这种类型的回退是优雅降级的一个示例。
地理隔离:你可以通过隔离特定地理区域使其彼此之间没有依赖关系,从而在你的服务中构建额外的可靠性。例如,如果你将北美和澳大利亚分为单独的服务区域,则由于流量过载而在澳大利亚发生的中断也不会影响你在北美的服务。请注意,地理隔离确实会增加成本:隔离这些地理区域也意味着澳大利亚无法借用北美的备用容量。
优雅降级:服务应该是“弹性的”,并且不会在过载条件和峰值下灾难性地失败——也就是说,即使并非一切正常,你也应该让你的应用程序做一些合理的事情。向用户提供有限的功能比错误页面更好。
集成测试:软件测试中的一个阶段,其中将各个软件模块组合在一起并作为一个组进行测试,以验证它们是否一起正确运行。这些“部分”可以是代码模块、单个应用程序、网络上的客户端和服务器应用程序等。集成测试通常在单元测试之后和最终验证测试之前执行。
运营就绪实践:旨在确保支持服务的团队知道在出现问题时如何有效响应,以及服务具有抵御中断能力而设计的练习。例如,Google 持续执行灾难恢复测试演练,以确保即使发生大规模灾难,其服务也能提供持续的正常运行时间。
推出策略:在服务推出(任何类型的软件组件或配置的部署)期间应用的一组原则,以减少推出早期阶段的中断范围。例如,推出策略可能指定推出应逐步进行,按照 5/20/100% 的时间表进行,以便只有当推出在没有问题的情况下通过第一个里程碑时,才能继续向更大比例的客户进行。大多数问题将在服务暴露给少量客户时显现出来,从而使你可以最大限度地减少损害范围。请注意,为了使推出策略在最大限度地减少损害方面有效,你必须建立快速回滚机制。
回滚:这是将先前推出(全部或部分)到给定服务或系统的一组更改恢复的能力。例如,你可以恢复配置更改,运行已知良好的二进制文件的先前版本等。
分片:将数据结构或服务拆分为分片是一种管理策略,它基于为单台机器的资源构建的系统无法扩展的原则。因此,你可以将 CPU、内存、磁盘、文件句柄等资源分布到多台机器上,以创建更大整体的更小、更快、更易于管理的部分。
尾部延迟: 当为服务的延迟(响应时间)设定目标时,人们很容易倾向于测量平均延迟。这种方法的**问题**在于,看起来可以接受的平均值可能会掩盖“长尾”中非常大的异常值,在这些异常值中,某些用户可能会遇到非常糟糕的响应时间。因此,SRE 的最佳实践是测量并设定第 95 百分位和/或第 99 百分位延迟的目标,目的是减少这种尾部延迟,而不仅仅是平均延迟。
感谢 Ben Lutch、Dave Rensin、Miki Habryn、Randall Bosetti 和 Patrick Bernier 的贡献。
1. Beyer, B., Jones, C., Petoff, J., Murphy, N. R. 2016. 站点可靠性工程:Google 如何运营生产系统. O'Reilly Media; https://landing.google.com/sre/book.html.
不可避免:你正在构建分布式系统 - Mark Cavage 构建分布式系统需要有条不紊的需求分析方法。 https://queue.org.cn/detail.cfm?id=2482856
今天的最终一致性:局限性、扩展和超越 - Peter Bailis 和 Ali Ghodsi, UC Berkeley 在不保证安全性的情况下,如何基于最终一致性基础设施构建应用程序? https://queue.org.cn/detail.cfm?id=2462076
与 Wayne Rosing 的对话 - David J. Brown Web 如何改变开发者构建和发布软件的方式。 https://queue.org.cn/detail.cfm?id=945162
Benjamin Treynor Sloss 六岁开始编程,十七岁加入 Oracle 担任软件工程师。他曾在 Oracle 和 Versant 担任软件工程师,然后在 E.piphany、SEVEN 工作,担任工程管理职位,最终加入 Google (2003 年至今)。 他目前在 Google 领导着约 4,200 人的团队,负责站点可靠性工程、网络和全球数据中心。
Mike Dahlin 是 Google 的杰出工程师,自 2013 年以来一直在 Google Cloud Platform 工作。 在加入 Google 之前,他是德克萨斯大学奥斯汀分校的计算机科学教授,他的研究兴趣包括互联网和大规模服务、容错、操作系统、分布式系统和存储系统。 他是 Fellow 和 IEEE Fellow。
Vivek Rau 是 Google 的 SRE 经理,也是 SRE 的发布协调工程子团队的创始成员。 在加入 Google 之前,他曾在 Citicorp Software、Versant 和 E.piphany 工作。 他目前管理着多个 SRE 团队,负责跟踪和改进 Google Cloud Platform 的可靠性。
Betsy Beyer 是 Google 的技术作家,专门从事站点可靠性工程。 她之前曾为 Google 在 Mountain View 及其全球分布式数据中心的数据中心和硬件运营团队编写文档。 她曾任斯坦福大学技术写作讲师。
版权 © 2017 归所有者/作者所有。 出版权已授权给 。
最初发表于 Queue vol. 15, no. 2—
在 数字图书馆 中评论这篇文章
Niklas Blum, Serge Lachapelle, Harald Alvestrand - WebRTC - 面向开放 Web 平台的实时通信
在这场疫情时期,世界比以往任何时候都更依赖于基于互联网的 RTC (实时通信)。 过去十年,RTC 产品数量呈爆炸式增长,这在很大程度上是由于更便宜的高速网络接入和更强大的设备,同时也归功于一个名为 WebRTC 的开放、免版税平台。 WebRTC 的作用正在从实现有用的体验,发展到成为让数十亿人能够继续工作和学习,并在疫情期间保持重要的人际联系的关键。 WebRTC 未来的机遇和影响确实令人着迷。
Benjamin Treynor Sloss, Shylaja Nukala, Vivek Rau - 重要的指标
衡量你的站点可靠性指标,设定正确的目标,并努力准确地衡量这些指标。 那么,你会发现你的服务运行得更好,中断更少,用户采用率更高。
Silvia Esparrachiari, Tanya Reilly, Ashleigh Rentz - 跟踪和控制微服务依赖关系
如果你曾将钥匙锁在家里或车里,你就会熟悉依赖循环。 没有钥匙你打不开锁,但不开锁你就拿不到钥匙。 有些循环很明显,但更复杂的依赖循环可能很难在它们导致中断之前被发现。 跟踪和控制依赖关系的策略对于维护可靠的系统是必要的。
Diptanu Gon Choudhury, Timothy Perrett - 为互联网规模服务设计集群调度器
寻求构建调度系统的工程师应考虑他们使用的底层基础设施的所有故障模式,并考虑调度系统的运维人员如何配置补救策略,同时帮助在租户系统所有者进行故障排除期间尽可能保持租户系统的稳定。