说到高并发和高可用,往往会引起很多人的兴趣,有时甚至会成为框架选择的噱头。事实上,它们往往与框架关系不大,而与架构有关。很多时候,老码农都会面临一个问题:“系统的服务可用性是多少?如何获取?”但是在思考这个问题之前,我们首先要明确一个概念,那就是——什么是服务可用性?可用性是系统处于可工作状态的时间比例,通常描述为任务可行性。从数学上讲,这相当于1减去不可用性。——维基百科相应的,我们的软件系统工作的时间比例就是服务的可用性,也就是说服务的可用性可以用百分比值来描述。我们经常用这个SLO(service-levelobjective,服务级别目标)来表示服务的可用性。至于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分钟的大范围中断和几次短暂的部分中断。这里的假设是:单个分片的故障不认为是整个系统的故障,整体可用性是根据分片可用性的加权和来计算的。5个独立的关键依赖,服务可用性99.999%。这五个碎片相互独立,不能相互转移。所有更改都是一个接一个地进行的,一次一个分片。当年服务中断的总预算是每年525,600分钟或53分钟(基于一年365天)的0.01%。分配给关键依赖项的服务中断预算是五个525,600分钟的0.001%,即525,600分钟或26分钟的0.005%。考虑关键依赖的服务中断,服务中断时间预算为53-26=27分钟。此外,估计中断次数为4(1次完全中断,3次中断仅影响一个分片),预期服务中断的总影响:(1x100%)+(3x20%)=1.6。那么,可用于检测中断并从中断中恢复的时间为27/1.6=17分钟。如果监控告警时间为2分钟,值班人员有5分钟时间调查告警,则有效解决问题的剩余时间为10分钟。提高可用性的方向仔细观察这些数字,可以发现三个主要因素可以使服务更可靠。通过工艺、测试、设计评审等手段,减少停机次数。通过分片、地理隔离、优雅降级或客户端隔离来减少停机时间的范围。减少恢复时间——通过监控、一键回滚等。可以在这三个方向之间进行权衡以便于实施。例如,如果17分钟的MTTR难以实现,则应着重于减少平均中断的范围。ServiceAvailabilityDependencyNesting一个无意的推论,依赖链中的每一个额外的环节是否都需要增加额外的可用性级别?例如,二级依赖需要两个额外的9,三级依赖需要三个额外的9,依此类推。这个推论是不正确的,它是基于一个依赖层次结构,即在每一层都有不断扇出的树一个具有许多独立的关键依赖的高可用服务系统显然是不现实的。无论它出现在依赖关系树中的什么位置,关键依赖关系本身都可能导致整个服务(或服务分片)失败。因此,如果给定的组件A表现为多个服务的依赖项,那么A应该只被计算一次,因为无论有多少中间服务受到影响,A的故障最终都会导致服务的故障。正确的依赖关系计算可能是这样的:如果一个服务有N个唯一的关键依赖关系,那么每个依赖关系将1/N贡献给由服务引起的不可用性,无论它在层次结构中的深度如何。每个依赖项仅被评估一次,即使它在依赖项层次结构中出现多次也是如此。例如,假设服务b的故障预算为0.01%。服务所有者愿意将一半预算花在他们自己的错误和损失上,另一半花在关键依赖项上。如果服务有N个这样的依赖项,则每个依赖项都会收到剩余故障预算的1/N。一个典型的服务通常有5到10个关键依赖,因此每个服务的故障率只有服务b的十分之一或二十分之一。因此,作为一般经验法则,必须向服务的关键依赖项添加额外的可用性。服务可用性的故障预算通常,故障预算用于平衡可用性和创新速度。该预算定义了一段时间内(通常是一个月)服务可接受的故障级别。故障预算只是1减去服务的SLO,因此99.99%可用的服务是0.01%故障的“预算”。开发团队可以在合理范围内自由发布新功能、更新等,只要服务没有花费当月的故障预算。如果使用故障预算,服务可能会冻结更改,但紧急安全修复和首先解决导致违规的更改除外。直到服务在预算中获得空间,或者时间重置。SLO与滑动窗口一起使用,因此故障预算逐渐增加。对于SLO大于99.99%的成熟服务,每季度重置预算是合适的,因为允许的停机时间最短。细目预算提供了一种通用的、数据驱动的机制来评估发布风险,消除运营和产品开发团队之间可能出现的结构紧张。细目预算还提供了一个共同目标,即在不“超出预算”的情况下实现更快的创新和更多的发布。提高服务可用性:减少关键依赖现在,是时候关注服务的依赖以及如何设计它们以减少和最小化关键依赖。关于关键依赖通常,任何关键组件的可用性必须是整个系统目标的10倍。因此,在理想的世界中,目标是使尽可能多的组件成为非依赖性的。这样做意味着组件可以遵守较低的可靠性标准,获得创新和承担风险的自由。减少关键依赖关系的最基本和最明显的策略是尽可能消除SPOF(单点故障)。较大的系统应该能够在没有任何非关键依赖项或SPOF给定组件的情况下正常运行。实际上,您可能无法摆脱所有关键依赖项,但您可以通过遵循一些围绕系统设计的最佳实践来优化可靠性。虽然并非总能做到这一点,但如果您在设计和规划阶段规划可靠性,而不是在系统运行并影响实际用户之后,就更容易、更有效地实现系统可靠性。在考虑新系统或服务时,以及重构或改进现有系统或服务时,架构/设计审查可以识别内部和外部依赖关系。如果服务使用共享基础设施(例如,多个用户可见产品使用的底层数据库服务),请考虑是否正确使用了该基础设施。将共享基础设施的所有者明确确定为额外的利益相关者。另外,注意不要使依赖项过载,仔细协调与这些依赖项的所有者的工作。有时,产品或服务取决于公司无法控制的因素,例如第三方提供的代码库、服务或数据,识别这些因素可以减少它们引入的不可预测性。冗余和隔离通过将依赖关系设计为具有多个独立实例来减轻对关键依赖关系的依赖。例如,如果在一个实例中存储数据可提供99.9%的数据可用性,那么在三个分布式实例中存储三个副本可提供九个九的理论可用性级别。在现实世界中,相关性永远不会为零,因此实际可用性永远不会接近九个九,但远高于三个九。如果一个系统或服务是“广泛分布的”,那么地理分隔并不总是无关紧要的。在附近位置使用多个系统可能比在更远的位置使用同一系统更好。同样,将RPC发送到集群中的一个服务器池可提供99.9%的可用性,但将三个并发RPC发送到三个不同的服务器池并接受第一个到达的响应有助于将可用性提高到远远超过三个9的级别。如果服务器池与RPC发送方的距离大致相等,则此策略还可以减少延迟。故障转移与回滚一个基本的经验法则是,当必须手动将故障转移联机时,故障预算可能已经超出。最好有一个故障安全切换,如果出现问题,软件可以自我隔离。在无法做到这一点的情况下,可以执行自动化脚本。同样,如果该问题依赖于一个人来检查,则满足SLO的机会很小。将人员纳入缓解计划会大大增加SLO的风险,并且需要构建系统以实现简单、快速和可靠的回滚。随着您的系统日趋成熟并且您对通过监控来检测问题更有信心,您可以通过将系统设计为自动触发安全回滚来减少MTTR。在可能的情况下,将依赖项设计为异步的,而不是同步的,这样它们就不会意外地变得不重要。如果服务等待来自其非关键依赖项之一的RPC响应,并且该依赖项的延迟显着增加,则此延迟将不必要地影响父服务的延迟。通过使RPC调用成为非关键异步依赖项,父服务的延迟与依赖项的延迟分离。虽然异步性可能会使代码和基础设施复杂化,但这种权衡可能是值得的。检查所有可能的故障模式检查每个组件和相关性并确定其故障的影响。以下问题可能是一些方向:如果服务的依赖项之一失败,服务能否继续以降级模式提供服务?换句话说,为优雅降级而设计。您如何处理在不同情况下不可用的依赖项?在服务启动时?在运行时?设计并实施一个健壮的测试环境,确保每个依赖项都有自己的测试覆盖率,并使用特定于环境的用例进行测试。以下是一些推荐的测试策略:使用集成测试来执行故障注入——验证系统是否可以在任何依赖项出现故障时幸存下来。进行灾难测试以识别弱点或隐藏的依赖关系。记录纠正发现的错误的后续行动。故意让系统过载,看看它是如何降级的。无论如何,都会测试系统对负载的响应;最好自己执行这些测试,而不是将负载测试留给用户。容量规划可确保在成本可接受的情况下正确供应和过度供应每个依赖项。如果可能,标准化依赖关系的配置以限制子系统之间的不一致并避免一次性故障模式。问题的检测、故障排除和诊断应尽可能简单,有效的监控是及时发现问题的关键组成部分。诊断具有严重依赖性的系统很困难,但总有一种无需操作员即可减轻故障的解决方案。期望随着规模的变化而变化,当在一台机器上以二进制形式启动的服务以更大的规模部署时,可能会有许多依赖关系,无论是否明显。每一个数量级的规模都会暴露出新的瓶颈,不仅是它自身的服务,还有它所依赖的服务。考虑一下如果依赖项没有按需要快速扩展会发生什么。另请注意,系统依赖性会随着时间的推移而发展,并且依赖性列表可能会随着时间的推移而增长。在基础架构方面,一个典型的设计是构建一个系统,该系统可以扩展到初始目标负载的10倍,而无需进行重大更改。结论服务的可用性并不高深莫测,它只是一个百分比数字。服务可用性指标(例如99.99%)通常令人不安,但并非无法实现。提供超过四个9的服务可用性,并不是超越常人的智慧,而是通过不断完善规则形成最佳实践,并综合应用。
