

# REL05-BP01 实施轻松降级以将适用的硬依赖关系转换为软依赖关系
<a name="rel_mitigate_interaction_failure_graceful_degradation"></a>

即使依赖项不可用，应用程序组件也应继续执行其核心功能。应用程序组件可以提供稍微陈旧的数据、替代数据，甚至没有数据。这可确保在提供核心业务价值的同时，将局部故障对整体系统功能造成的障碍减至最少。

 **期望的结果：** 某个组件的依赖项运行不正常时，该组件仍可在性能降低的条件下运行。组件的故障模式应视为正常运行。工作流在设计时，应确保此类故障不会导致完全失败，或者至少实现可预测和可恢复的状态。 

 **常见反模式：** 
+  未确定所需的核心业务功能。即使在依赖项故障期间也不测试组件是否正常运行。 
+  不论是出错时，还是当多个依赖项中只有一个不可用且仍可以返回部分结果时，不提供任何数据。 
+  在事务部分失败时造成不一致的状态。 
+  没有替代方法用于访问中央 Parameter Store。 
+  在刷新失败时，使本地状态失效或清空，而没有考虑这样做的后果。 

 **建立此最佳实践的好处：** 轻松降级可以提高整个系统的可用性，即使在故障期间也能保持最重要功能的功能。 

 **未建立这种最佳实践的情况下暴露的风险等级：** 高 

## 实施指导
<a name="implementation-guidance"></a>

 实施轻松降级有助于最大限度地减少依赖项故障对组件功能的影响。理想情况下，组件检测依赖项故障，并以对其他组件或客户影响最小的方式解决这些故障。 

 为轻松降级设计架构意味着在依赖项设计期间，需要考虑潜在的故障模式。对于每种故障模式，都要有办法向调用方或客户提供组件的大部分功能，或者至少提供最关键的功能。这些注意事项可以作为额外的要求进行测试和验证。理想情况下，即使一个或多个依赖项出现故障，一个组件也能够以可接受的方式执行其核心功能。 

 这既是商业议题，也是技术议题。所有业务要求都很重要，应尽可能满足。但是，确定在无法满足所有要求时会出现什么情况，这种做法同样很有意义。系统可以设计为具备可用性和一致性，但是如果必须放弃一个要求，那么哪个要求更重要？ 对于付款处理而言，可能一致性更重要。对于实时应用程序，可能可用性更重要。对于面向客户的网站，这个回答可能取决于客户的期望值。 

 其具体的意义取决于组件的要求，以及将什么功能视为其核心功能。例如： 
+  在登录页面上，电子商务网站可能会显示来自多个不同系统的数据，例如个性化推荐、最热销的产品和客户订单状态。当一个上游系统出现故障时，合理的做法是显示其余的所有信息，而不是向客户显示一个错误页面。 
+  对于执行批量写入的组件，如果某个单独的操作失败，它仍应继续执行批处理。实施重试机制应该很简单。要实施重试机制，可以向调用方返回哪些操作成功、哪些操作失败的信息，或者将失败的请求放入死信队列中来实施异步重试。同时还应记录有关失败操作的信息。 
+  处理事务的系统必须确保，要么执行了所有更新，要么未执行任何更新。对于分布式事务，在同一个事务后面的操作失败时，可以使用 Sega 模式来回滚先前的操作。这里的核心功能是保持一致性。 
+  时间关键型系统应能够处理未及时响应的依赖项。在这些情况下，可以使用断路器模式。当来自依赖项的响应开始超时时，系统可以切换为关闭状态，不进行额外的调用。 
+  应用程序可以从 Parameter Store 中读取参数。创建具有默认参数集的容器镜像，并在 Parameter Store 不可用时使用这些镜像，这种做法会很有用。 

 请注意，需要对组件出现故障时所采取的途径进行测试，而且这一途径应该比主要途径简单得多。通常，[应避免使用回退策略](https://aws.amazon.com/builders-library/avoiding-fallback-in-distributed-systems/)。 

## 实施步骤
<a name="implementation-steps"></a>

 确定外部和内部依赖项。考虑它们可能出现什么样的故障。思考在故障期间，尽可能减少对上游和下游系统以及客户的负面影响的方法。 

 以下是依赖项列表以及在依赖项故障时如何轻松降级： 

1.  **依赖项部分故障：** 一个组件可以向下游系统发出多个请求，这可以是向一个系统发出多个请求，也可以是向多个系统发出一个请求。根据具体的业务环境，可能需要采用不同的处理方式（有关更多详细信息，请参阅实施指南前文中的示例）。 

1.  **由于高负载，下游系统无法处理请求：** 如果发送到下游系统的请求持续失败，则继续重试就毫无意义。这可能会对已经过载的系统造成额外负载，使得系统更难于恢复。这时可以使用断路器模式，该模式监视对下游系统的失败调用。如果大量调用失败，它会停止向下游系统发送更多请求，仅不定期让调用通过，以测试下游系统是否再次可用。 

1.  **Parameter Store 不可用：** 要转换 Parameter Store，可以使用容器或机器镜像中包含的软依赖项缓存或合理默认值。请注意，这些默认值需要保持为最新并包含在测试套件中。 

1.  **监控服务或其他非功能性依赖项不可用：** 如果某个组件间歇性地无法将日志、指标或跟踪发送到中央监控服务，通常最好还是正常执行业务功能。长时间静默地不进行日志记录或不推送指标通常不可接受。此外，某些使用场景可能需要完整的审计条目才能满足合规性要求。 

1.  **关系数据库的主实例可能不可用：** 与几乎所有关系数据库一样，Amazon Relational Database Service 只能有一个主写入器实例。对于写入工作负载，这会造成单点故障，并增加扩缩的难度。使用多可用区配置来实现高可用性，或者使用 Amazon Aurora 无服务器架构来实现更好的扩展能力，可以部分缓解这种情况。对于非常高的可用性要求，完全不依赖于主写入器是有意义的。对于只读查询，可以使用只读副本，这提供了冗余和横向扩展能力，而不仅仅是纵向扩展。对写入操作可以进行缓冲，例如缓冲在 Amazon Simple Queue Service 队列中，这样即使主写入器暂时不可用，仍然可以接受来自客户的写入请求。 

## 资源
<a name="resources"></a>

 **相关文档：** 
+  [Amazon API Gateway：限制 API 请求以提高吞吐量](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html) 
+  [CircuitBreaker（对《发布它！》一书中的“断路器”部分进行的总结）](https://martinfowler.com/bliki/CircuitBreaker.html) 
+  [AWS 中的错误重试和指数回退](https://docs.aws.amazon.com/general/latest/gr/api-retries.html) 
+  [Michael Nygard《发布它！ 设计和部署生产就绪的软件》（Release It\! Design and Deploy Production-Ready Software）](https://pragprog.com/titles/mnee2/release-it-second-edition/) 
+  [Amazon Builders' Library：避免在分布式系统中回退](https://aws.amazon.com/builders-library/avoiding-fallback-in-distributed-systems) 
+  [Amazon Builders' Library：避免无法克服的队列积压](https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs) 
+  [Amazon Builders' Library：缓存挑战和策略](https://aws.amazon.com/builders-library/caching-challenges-and-strategies/) 
+  [Amazon Builders' Library：为超时、重试和回退引入抖动](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/) 

 **相关视频：** 
+  [重试、回退和抖动：AWS re:Invent 2019：介绍 Amazon Builders’ Library（DOP328）](https://youtu.be/sKRdemSirDM?t=1884) 

 **相关示例：** 
+  [Well-Architected 实验室：第 300 级：实施运行状况检查和管理依赖项以提高可靠性](https://wellarchitectedlabs.com/Reliability/300_Health_Checks_and_Dependencies/README.html) 