该文章转自: 搜狐文章 链接
谈到高并发和高可用往往引起很多人的兴趣,有时候成为框架选择的噱头。实际上,它们往往和框架关系不大,而是跟架构息息相关。在很多时候,老码农会直面一个问题:
“系统的服务可用性是多少?是怎么得来?”
但在思考这个问题之前,先要澄清一个概念,那就是——
什么是服务可用性
可用性就是一个系统处在可工作状态的时间的比例,这通常被描述为任务可行率。数学上来讲,相当于1减去不可用性。——wiki 百科
相应的,我们的软件系统处于可工作的时间比例,就是服务的可用性,也就是说,服务可用性可以描述为一个百分比的数值。我们经常用这个SLO(service-level objective ,服务级目标)来代表服务可用性,至于SLO,SLA,SLI 等概念之间的差异,这里暂不做深入讨论。
SLO用数字来定义可用性对于特定服务的意义,来表示服务几乎总是活着,总是处于可以快速运行的状态。制定SLO是根据如下:
绝大多数软件服务和系统的目标应该是近乎完美的可用性,而不是完美的可用性。服务可用性一般是99.999% 或99.99% ,而不是100%,因为用户无法区分服务是100% 可用和不“完美”可用之间的区别。在用户和服务之间还有许多其他的系统,例如笔记本电脑、家庭 WiFi、互联网等等 ,这些系统的可用性远远低于100% 。因此,99.99% 和100% 之间的边际差异在其他不可用性的噪音中丢失了,并且,即使为增加最后一部分可用性付出了巨大努力,用户也可能没有从中获得任何好处。
很多云服务的目标是向用户提供99.99% 的可用性(就是我们常说的“四个9”)。一些服务在外部承诺较低的数字,但在内部可能设定了99.99% 的目标。作为SLA,这个严格的目标描述了用户在违反合同之前对服务性能不满意的情况,因为软件服务的首要目标是让用户满意。对于许多服务,99.99% 的内部目标代表了平衡成本、复杂性和可用性的最佳位置。
服务可用性解读
服务可用性是中断频率和持续时间的函数。它是通过以下方式衡量的: * 停机频率,或者是它的倒数: MTTF (平均停机时间)。* 持续时间,使用 MTTR (平均修复时间)。持续时间根据用户的经历定义: 从故障开始持续到正常行为恢复。
因此,可用性在数学上定义为使用适当单位的 MTTF / (MTTF + MTTR)。
四个9的服务可用性可能是很多软件系统的目标,如何达到这一目标呢?需要先明确一下导致服务不可用的来源。服务不可用有两个主要来源: 服务本身的问题和服务的关键依赖的问题。关键依赖是指如果出现故障,就会导致服务相应故障的依赖项。
关键依赖
服务的可用性不能超过其所有关键依赖关系的交集。如果服务的目标是提供99.99% 的可用性,那么所有的关键依赖项必须远远超过99.99% 的可用性。据说在谷歌内部,使用这样一个经验法则: 因为任何服务都有几个关键依赖项,以及自身的特殊问题,关键依赖必须提供一个与服务相关的额外9% 的可用性(这里为99.999%) 。
如果有一个关键依赖,一个相对常见的挑战是没有提供足够的可用性,就必须采取措施来增加依赖项的有效可用性(例如,通过缓存、限流、优雅降级等等)。
降低期望
从数学上看,服务的可用性不能超过其事件频率乘以其检测和恢复时间。例如,每年有4次完全宕机,每次持续15分钟,结果总共是60分钟。即使该服务在这一年中剩下的时间里都运行良好,99.99% 的可用性(每年停机时间不超过53分钟)也是没有达的。
如果服务被依赖于无法提供相应水平的可用性级别,那么就应该努力纠正这种情况,可以通过增加自身服务的可用性等级,或者如前所述的增加缓解措施。降低期望值(即公布的可用性)也是一种选择,而且往往是正确的选择: 向相关服务明确表示,它应该重新设计系统以弥补我们服务可用性,或者降低自己的目标。如果不纠正或解决这种差异,可用性将无法达到要求。
服务可用性的计算
考虑一个目标可用性为99.99% 的示例服务,并处理依赖项和停机响应的需求。
一个例子
假设这个99.99% 的可用服务具有以下特征:
- 每年一次大停机和三次小停机。这些数字听起来很高,但是99.99% 的可用性目标确实意味着每年有20到30分钟的大范围停机和几次短暂的部分停机。这里的假设是: 单个分片的失败并不被认为是整个系统的失败,总体可用性是根据分片可用性的加权和来计算的。
- 有五个独立关键依赖, 服务可用性为99.999%。
- 这五个相互独立的碎片,不能相互转移。
- 所有变更逐个进行,每次一个分片。
那么,本年度服务中断的总预算为每年525,600分钟的的0.01%或53分钟(以每年365天为基础)。分配给关键依赖的服务中断预算是5个525,600分钟的0.001%,即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分钟的话,则有效解决问题的剩余时间是 10分钟。
提高可用性的方向
仔细研究这些数字,有三个主要的因素可以使服务更可靠。
- 透过流程、测试、设计review等手段,减少宕机的次数。
- 通过分片、地理隔离、优雅降级或客户隔离,缩小停机范围。
- 缩短恢复时间ーー透过监控、一键式回滚等。
可以在这三个方向之间进行权衡,以便实现更加容易。例如,如果17分钟的 MTTR 很难实现,那么应该将精力集中在减少平均停用的范围上。
服务可用性之依赖嵌套
一个不经意的推断,依赖链中的每个额外链接都需要增加额外的可用性等级么?例如,二级依赖需要两个额外的9,三级依赖需要三个额外的9,以此类推。
这种推论是不正确的,它基于依赖关系层次结构,即在每个级别上具有常量扇出的树 具有许多独立关键依赖的高可用性服务系统显然是不现实的。
无论在依赖项树中出现在哪里,关键依赖项本身都可能导致整个服务(或服务分片)失败。因此,如果给定的组件A表现为几个服务的依赖项,那么 A应该只计算一次,因为无论有多少中间的服务受到影响,A的故障终将导致服务的故障。
正确的依赖计算可能是这样的:
- 如果一个服务具有N个唯一的关键依赖项,那么每个依赖对服务导致的不可用性贡献1 / N,而不管它在层次结构中的深度如何。
- 即使它在依赖项层次结构中出现多次,每个依赖也只计算一次。
例如,假设服务b 的故障预算为0.01% 。服务拥有者愿意花一半的预算在他们自己的 bug 和损失上,另一半花在关键依赖上。如果服务有 N个这样的依赖项,每个依赖项接收剩余故障预算的1 / N。典型的服务通常有5到10个关键依赖项,因此每个服务的失败率只有服务b 的十分之一或二十分之一。因此,根据一般经验,服务的关键依赖项必须增加额外的可用性。
服务可用性之故障预算
一般地,使用故障预算来平衡可用性和创新速度。这个预算定义了在一段时间内(通常是一个月)服务可接受的故障水平。故障预算只是1减去服务的 SLO,因此,99.99% 可用的服务是故障为0.01% 的“预算”。只要服务没有花费当月的故障预算,开发团队就可以在合理范围内自由地发布新特性、更新等等。
如果使用了故障预算,除了紧急安全修复和解决最初导致违规的更改之外,服务可能将冻结变更。直到服务在预算中赢得了空间,或者时间重置。使用 SLOs 滑动窗口,因此故障预算逐渐增加。对于 SLO 大于99.99% 的成熟服务,每季度重置预算是适当的,因为允许的停机时间很小。
故障预算提供了一个共同的、数据驱动的机制来评估发布风险,从而消除了可能在运维团队和产品开发团队之间产生的结构性紧张。故障预算还提供了一个共同的目标,在不“超出预算”的情况下实现更快的创新和更多的发布。
提高服务可用性:减少关键依赖
现在,可以重点讨论服务的依赖关系,如何进行设计以减少并最小化关键依赖。
关于关键依赖
一般的,任何关键部件的可用性必须是整个系统目标的10倍。因此,在一个理想的世界中,目标是使尽可能多的组件成为非依赖的。这样做意味着组件可以坚持较低的可靠性标准,获得创新和承担风险的自由。
减少关键依赖性的最基本和最明显的策略是尽可能消除 SPOFs (单点故障)。较大的系统应该能够在没有任何非关键依赖项或 SPOF 的给定组件的情况下可以可接受地运行。
实际上,您可能无法摆脱所有关键的依赖关系,但是您可以遵循一些围绕系统设计的最佳实践来优化可靠性。虽然这样做并不总是可行的,但是如果你在设计和规划阶段计划可靠性,而不是在系统运行并影响实际用户之后,那么实现系统可靠性就会更容易和更有效。
当考虑一个新的系统或服务时,当重构或改进一个现有系统或服务时,一个架构/设计评审可以识别出内部与外部的依赖。如果服务使用的是共享基础设施(例如,多个用户可见产品使用的基础数据库服务) ,要考虑该基础设施是否得到正确使用。明确地将共享基础结构的所有者确定为附加的利益相关者。另外,要注意不要让依赖关系超载,小心地与这些依赖关系的所有者协调工作。
有时,产品或服务取决于公司无法控制的因素,例如,代码库、第三方提供的服务或数据,要识别这些因素可以减少它们带来的不可预测性。
冗余和隔离
通过将依赖设计为具有多个独立实例来减轻对关键依赖的依赖。例如,如果在一个实例中存储的数据提供了该数据99.9% 的可用性,那么在三个分布的实例中存储三个副本提供了9个9的理论可用性级别。
在现实世界中,相关性永远不会为零,因此实际可用性不会接近9个9,而是远远高于3个9。如果一个系统或服务是“广泛分布的” ,地理上的分离并不总是不相关的。在邻近地点使用多个系统,可能比在较远地点使用同一个系统更好。
类似地,向一个集群中的一个服务器池发送 RPC可以提供99.9% 的可用性,但是向三个不同的服务器池发送三个并发 RPC 并接受到达的第一个响应,这样有助于将可用性提高到远远超过三个9的级别。如果服务器池与 RPC 发送方的距离大致相等,那么这种策略还可以减少延迟。
故障切换与回滚
一个的基本经验是,当必须人工在线引发故障切换时,可能已经超出了故障预算。最好进行故障的安全切换,如果出现问题,这些软件可以自动隔离。在无法实现的情况下,可以执行自动脚本。同样,如果问题依赖于某一个人来检查,那么满足SLO 的机会会很小。
将人引入缓解计划大大增加了 SLO 的风险,需要构建方便、快速而可靠回滚的系统。随着系统逐渐成熟,并且对检测问题的监视获得了信心,就可以通过设计系统自动触发安全回滚来降低 MTTR。
在可能的情况下,将依赖项设计为异步的,而不是同步的,这样它们就不会意外地变得非常重要。如果服务等待来自其非关键依赖项之一的 RPC 响应,并且该依赖项的延迟会大大增加,那么这种延迟将不必要地影响父服务的延迟。通过将 RPC 调用设置为非关键的异步依赖项,可以将父服务的延迟与依赖项的延迟解耦。虽然异步性可能会使代码和基础结构复杂化,但这种权衡可能是值得的。
检查所有可能的失效模式
检查每个组件和依赖项,并确定其故障的影响。以下问题可能是一些方向:
- 如果其中一个依赖项失败,服务能否继续以降级模式提供服务?换句话说,为优雅的降级而设计。
- 如何处理在不同情况下依赖项不可用的问题?在服务启动时?在运行期间?
设计和实现一个健壮的测试环境,确保每个依赖项都有自己的测试覆盖率,并且使用专门针对环境的用例进行测试。以下是一些推荐的测试策略:
- 使用集成测试执行故障注入ーー验证系统能否在任何依赖关系发生故障时幸存下来。
- 进行灾难测试以识别弱点或隐藏的依赖关系。记录后续行动,以纠正发现的bug。
- 故意让系统过载,看看它是如何退化的。无论如何,系统对负载的响应都将被测试; 最好是自己执行这些测试,而不是将负载测试留给用户。
容量规划
确保每个依赖项都得到了正确的供给,如果成本可以接受,就过度供给。如果可能,将依赖项的配置标准化,以限制子系统之间的不一致性,并避免一次性的故障模式。
检测、故障排除和诊断问题要尽可能简单,有效的监测是能够及时发现问题的关键组成部分。诊断具有严重依赖关系的系统是困难的,但总是有一个不需要操作员就可以减轻故障的方案。
期待随着规模而来的变化,当在一台机器上以二进制文件开始的服务在更大的规模上部署时,可能会有许多明显或不明显的依赖关系。每一个规模的数量级都会暴露出新的瓶颈, 不仅仅是自己服务,还有所依赖的服务。考虑一下,如果依赖项不能像所需要的那样快速扩展,将会发生什么。
还要注意,系统依赖关系会随着时间的推移而发展,并且依赖关系的列表可能会随着时间的推移而增长。在基础设施方面,一个典型的设计是建立一个不需要重大变更就可以扩展到初始目标负载10倍的系统。
结束语
服务的可用性并不高深莫测,它只是一个百分比的数字。服务可用性的指标(例如99.99%)往往令人不安,但并非不可实现。提供超过四个9的服务可用性,不是通过超出常人的智慧,而是通过不断地完善规则形成最佳实践,并且全面应用。