Google Cloud 架构框架中的这份文档提供了用于构建服务的设计原则,以便它们能够容忍故障并根据客户需求进行扩展。当对服务的需求很高或发生维护事件时,可靠的服务会继续响应客户的请求。以下可靠性设计原则和最佳实践应该是您的系统架构和部署计划的一部分。
创建冗余以提高可用性
具有高可靠性需求的系统必须没有单点故障,并且它们的资源必须跨多个故障域进行复制。故障域是可以独立发生故障的资源池,例如 VM 实例、专区或区域。当您跨故障域进行复制时,您可以获得比单个实例更高的聚合级别的可用性。有关更多信息,请参阅区域和可用区。
作为可能成为系统架构一部分的冗余的具体示例,为了将 DNS 注册中的故障隔离到各个区域,请为同一网络上的实例使用区域 DNS 名称以相互访问。
设计具有故障转移功能的多区域架构以实现高可用性
通过将应用程序架构为使用分布在多个区域的资源池,并在区域之间进行数据复制、负载平衡和自动故障转移,使您的应用程序对区域故障具有弹性。运行应用程序堆栈每一层的区域副本,并消除架构中的所有跨区域依赖关系。
跨区域复制数据以进行灾难恢复
将数据复制或存档到远程区域,以便在发生区域中断或数据丢失时进行灾难恢复。使用复制时,恢复更快,因为远程区域的存储系统已经拥有几乎是最新的数据,除了可能由于复制延迟而丢失少量数据。当您使用定期存档而不是连续复制时,灾难恢复涉及从新区域中的备份或存档中恢复数据。与激活持续更新的数据库副本相比,此过程通常会导致更长的服务停机时间,并且由于连续备份操作之间的时间间隔,可能会导致更多的数据丢失。无论使用哪种方法,都必须在新区域中重新部署和启动整个应用程序堆栈,并且在这种情况下服务将不可用。
有关灾难恢复概念和技术的详细讨论,请参阅为云基础架构中断构建灾难恢复。
设计多区域架构以应对区域中断
如果您的服务即使在整个区域发生故障的极少数情况下也需要持续运行,请将其设计为使用分布在不同区域的计算资源池。运行应用程序堆栈每一层的区域副本。
在区域出现故障时使用跨区域的数据复制和自动故障转移。一些 Google Cloud 服务具有多区域变体,例如 BigQuery 和 Cloud Spanner。为了应对区域故障,请尽可能在您的设计中使用这些多区域服务。有关区域和服务可用性的更多信息,请参阅 Google Cloud 位置。
确保不存在跨区域依赖关系,以便区域级故障的影响范围仅限于该区域。
消除区域单点故障,例如在无法访问时可能导致全局中断的单区域主数据库。请注意,多区域架构通常成本更高,因此在采用此方法之前请考虑业务需求与成本。
有关跨故障域实施冗余的进一步指导,请参阅调查文件云应用程序的部署原型 (PDF)。
消除可扩展性瓶颈
识别不能超出单个 VM 或单个区域的资源限制的系统组件。一些应用程序垂直扩展,您可以在单个 VM 实例上添加更多 CPU 内核、内存或网络带宽来处理负载的增加。这些应用程序的可扩展性受到严格限制,您必须经常手动配置它们以应对增长。
如果可能,重新设计这些组件以水平扩展,例如跨 VM 或区域进行分片或分区。要处理流量或使用量的增长,您需要添加更多分片。使用可以自动添加的标准 VM 类型来处理每个分片负载的增加。有关更多信息,请参阅可扩展和弹性应用程序的模式。
如果您无法重新设计应用程序,您可以将由您管理的组件替换为完全托管的云服务,这些云服务旨在水平扩展而无需用户操作。
过载时优雅地降低服务水平
设计您的服务以容忍过载。服务应该检测过载并向用户返回质量较低的响应或部分丢弃流量,而不是在过载下完全失败。
例如,服务可以使用静态网页响应用户请求,并暂时禁用处理成本更高的动态行为。此行为在从 Compute Engine 到 Cloud Storage 的热故障转移模式中有详细说明。或者,该服务可以允许只读操作并暂时禁用数据更新。
当服务降级时,应通知操作员纠正错误情况。
防止和缓解流量高峰
不要跨客户端同步请求。在同一时刻发送流量的客户端过多会导致流量峰值,从而可能导致级联故障。
在服务器端实施峰值缓解策略,例如节流、排队、减载或断路、优雅降级和优先处理关键请求。
客户端的缓解策略包括客户端限制和带抖动的指数退避。
清理和验证输入
为防止导致服务中断或安全漏洞的错误、随机或恶意输入,请清理和验证 API 和操作工具的输入参数。例如,Apigee 和 Google Cloud Armor 可以帮助防止注入攻击。
定期使用模糊测试,其中测试工具故意调用具有随机、空或太大输入的 API。在隔离的测试环境中进行这些测试。
操作工具应在更改推出之前自动验证配置更改,并在验证失败时拒绝更改。
以保留功能的方式进行故障保护
如果由于问题而出现故障,则系统组件应以允许整个系统继续运行的方式发生故障。这些问题可能是软件错误、错误的输入或配置、计划外的实例中断或人为错误。您的服务流程有助于确定您是否应该过度宽容或过于简单化,而不是过度限制。
考虑以下示例场景以及如何响应失败:
- 对于配置错误或空配置的防火墙组件,通常最好在操作员修复错误时失败打开并允许未经授权的网络流量在短时间内通过。此行为使服务保持可用,而不是失败关闭并阻止 100% 的流量。该服务必须依赖于应用程序堆栈中更深层次的身份验证和授权检查,以在所有流量通过时保护敏感区域。
- 但是,控制对用户数据的访问的权限服务器组件最好关闭失败并阻止所有访问。当配置损坏时,此行为会导致服务中断,但可以避免在打开失败时泄露机密用户数据的风险。
在这两种情况下,故障都应该引发高优先级警报,以便操作员可以修复错误情况。服务组件应该在失败打开方面犯错,除非它给业务带来极大风险。
将 API 调用和操作命令设计为可重试
API 和操作工具必须尽可能使调用重试安全。许多错误情况的一种自然方法是重试前一个操作,但您可能不知道第一次尝试是否成功。
您的系统架构应该使操作具有幂等性——如果您连续两次或多次对一个对象执行相同的操作,它应该产生与单次调用相同的结果。非幂等动作需要更复杂的代码来避免系统状态的损坏。
识别和管理服务依赖项
服务设计者和所有者必须维护对其他系统组件的完整依赖列表。服务设计还必须包括从依赖失败中恢复,或者如果完全恢复不可行,则优雅降级。考虑到系统使用的云服务的依赖关系和外部依赖关系,例如第三方服务 API,认识到每个系统依赖关系都有非零故障率。
当您设置可靠性目标时,请认识到服务的 SLO 在数学上受到其所有关键依赖项的 SLO 的约束。您不能比依赖项之一的最低 SLO 更可靠。有关详细信息,请参阅服务可用性的计算。
启动依赖
服务启动时的行为与其稳态行为不同。启动依赖项可能与稳态运行时依赖项有很大不同。
例如,在启动时,服务可能需要从它很少再次调用的用户元数据服务加载用户或帐户信息。当许多服务副本在崩溃或例行维护后重新启动时,副本会急剧增加启动依赖项的负载,尤其是当缓存为空且需要重新填充时。
在负载下测试服务启动,并相应地提供启动依赖项。考虑通过保存从关键启动依赖项中检索到的数据的副本来优雅降级的设计。此行为允许您的服务使用可能过时的数据重新启动,而不是在关键依赖项出现中断时无法启动。您的服务可以稍后在可行的情况下加载新数据以恢复正常操作。
在新环境中引导服务时,启动依赖项也很重要。使用分层架构设计您的应用程序堆栈,层之间没有循环依赖关系。循环依赖似乎是可以容忍的,因为它们不会阻止对单个应用程序的增量更改。但是,在灾难导致整个服务堆栈瘫痪后,循环依赖可能会导致难以或不可能重新启动。
最小化关键依赖
最小化您的服务的关键依赖项的数量,即其他组件的故障将不可避免地导致您的服务中断。为了使您的服务对它所依赖的其他组件的故障或缓慢具有更强的弹性,请考虑以下示例设计技术和原则,以将关键依赖项转换为非关键依赖项:
- 增加关键依赖项中的冗余级别。添加更多副本可以降低整个组件不可用的可能性。
- 对其他服务使用异步请求而不是阻塞响应,或者使用发布/订阅消息将请求与响应分离。
- 缓存来自其他服务的响应以从短期不可用的依赖项中恢复。
为了减少服务中的故障或缓慢对依赖它的其他组件的危害,请考虑以下示例设计技术和原则:
- 使用优先请求队列,并为用户等待响应的请求提供更高的优先级。
- 从缓存中提供响应以减少延迟和负载。
- 以保留功能的方式进行故障保护。
- 当流量过载时优雅地降级。
- 确保每次更改都可以回滚
如果没有明确定义的方法来撤消对服务的某些类型的更改,请更改服务的设计以支持回滚。定期测试回滚过程。每个组件或微服务的 API 都必须进行版本控制,并具有向后兼容性,这样前几代客户端才能随着 API 的发展继续正常工作。此设计原则对于允许逐步推出 API 更改以及在必要时快速回滚至关重要。
为移动应用程序实施回滚可能代价高昂。Firebase Remote Config 是一项 Google Cloud 服务,可让功能回滚变得更容易。
您不能轻易回滚数据库架构更改,因此请分多个阶段执行它们。设计每个阶段以允许应用程序的最新版本和先前版本的安全模式读取和更新请求。如果最新版本出现问题,这种设计方法可以让您安全地回滚。
建议
要将架构框架中的指南应用于您自己的环境,请遵循以下建议:
- 在客户端应用程序的错误重试逻辑中使用随机化实现指数退避。
- 实施具有自动故障转移的多区域架构以实现高可用性。
- 使用负载平衡在分片和区域之间分配用户请求。
- 设计应用程序以在过载情况下优雅降级。提供部分响应或提供有限的功能,而不是完全失败。
- 为容量规划建立数据驱动的流程,并使用负载测试和流量预测来确定何时配置资源。
- 建立灾难恢复程序并定期对其进行测试。