

# 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 Serverless 无服务器架构来实现更好的扩展能力，可以部分缓解这种情况。对于非常高的可用性要求，完全不依赖于主写入器是有意义的。对于只读查询，可以使用只读副本，这提供了冗余和横向扩展能力，而不仅仅是纵向扩展。对写入操作可以进行缓冲，例如缓冲在 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 (summarizes Circuit Breaker from “Release It\$1” book)](https://martinfowler.com/bliki/CircuitBreaker.html) 
+  [Error Retries and Exponential Backoff in AWS](https://docs.aws.amazon.com/general/latest/gr/api-retries.html) 
+  [Michael Nygard “Release It\$1 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/) 

 **相关视频：**
+  [Retry, backoff, and jitter: AWS re:Invent 2019: Introducing The Amazon Builders' Library (DOP328)](https://youtu.be/sKRdemSirDM?t=1884) 