

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用退避模式重试
<a name="retry-backoff"></a>

## 意图
<a name="retry-backoff-intent"></a>

使用退避模式的重试可以透明地重试因暂时错误而失败的操作，从而提高应用程序稳定性。

## 动机
<a name="retry-backoff-motivation"></a>

在分布式架构中，暂时性错误可能是由服务限制、网络连接暂时中断或服务暂时不可用引起的。自动重试因这些暂时错误而失败的操作可改善用户体验和应用程序韧性。但是，频繁重试可能会使网络带宽过载并导致争用。指数回退是一种通过增加指定重试次数的等待时间来重试操作的技术。

## 适用性
<a name="retry-backoff-applicability"></a>

在以下情况下，使用回退重试模式：
+ 您的服务经常限制请求以防止过载，从而导致调用过程出现 *429 请求过多*异常。
+ 在分布式架构中，网络是一个看不见的参与者，而暂时的网络问题会导致故障。
+ 被调用的服务暂时不可用，导致故障。除非使用此模式引入回退超时，否则频繁重试可能会导致服务降级。

## 问题和注意事项
<a name="retry-backoff-issues"></a>
+ **幂等性**：如果对该方法的多次调用与对系统状态的单个调用具有相同的效果，则该操作视为幂等的。使用回退重试模式时，操作应该是幂等操作。否则，部分更新可能会损坏系统状态。
+ **网络带宽**：如果重试次数过多占用网络带宽，则可能会导致服务降级，从而造成响应时间变慢。
+ **快速失效机制场景**：对于非瞬态错误，如果可以确定故障原因，则使用断路器模式快速失效会更有效。
+ **回退率**：引入指数回退可能会影响服务超时，从而导致最终用户的等待时间更长。

## 实现
<a name="retry-backoff-implementation"></a>

### 高级架构
<a name="retry-high-level-arch"></a>

下图说明了在返回成功响应之前，服务 A 如何重试对服务 B 的调用。如果服务 B 在尝试几次后仍未返回成功的响应，则服务 A 可以停止重试并将失败返回给其调用方。

![回退重试模式的高级架构](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/retry-backoff-1.png)


### 使用 AWS 服务来实施
<a name="retry-aws-services"></a>

下图显示了客户支持平台上的工单处理工作流程。通过自动升级工单优先级，可以加快不满意客户的工单。`Ticket info` Lambda 函数提取工单详细信息并调用 `Get sentiment` Lambda 函数。`Get sentiment` Lambda 函数通过将描述传递给 [Amazon Comprehend](https://aws.amazon.com/comprehend/)（未显示）来检查客户的情绪。

如果对 `Get sentiment` Lambda 函数的调用失败，则工作流程将重试该操作三次。 AWS Step Functions 允许您配置回退值，从而实现指数回退。

在此示例中，配置了最多重试三次，并设置了 1.5 秒的递增乘数。如果第一次重试发生在 3 秒之后，则第二次重试发生在 3 x 1.5 秒 = 4.5 秒之后，第三次重试发生在 4.5 x 1.5 秒 = 6.75 秒之后。如果第三次重试失败，则工作流程将失败。回退逻辑不需要任何自定义代码 – 它由 AWS Step Functions 作为配置提供。

![使用 AWS 服务的回退重试模式](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/retry-backoff-2.png)


### 代码示例
<a name="retry-sample-code"></a>

以下代码显示了回退重试模式的实施。

```
public async Task DoRetriesWithBackOff()
  {
     int retries = 0;
     bool retry;
     do
     {
       //Sample object for sending parameters
       var parameterObj = new InputParameter { SimulateTimeout = "false" };
       var content = new StringContent(JsonConvert.SerializeObject(parameterObj), 
                                System.Text.Encoding.UTF8, "application/json");
       var waitInMilliseconds = Convert.ToInt32((Math.Pow(2, retries) - 1) * 100);
       System.Threading.Thread.Sleep(waitInMilliseconds);
       var response =  await _client.PostAsync(_baseURL, content);
       switch (response.StatusCode)
       {
         //Success
         case HttpStatusCode.OK:
           retry = false;
           Console.WriteLine(response.Content.ReadAsStringAsync().Result);
           break;
         //Throttling, timeouts 
         case HttpStatusCode.TooManyRequests:
         case HttpStatusCode.GatewayTimeout:
           retry = true;
           break;
         //Some other error occured, so stop calling the API
         default:
           retry = false;
           break;
       }
       retries++;
     } while (retry && retries < MAX_RETRIES);
  }
```

### GitHub 存储库
<a name="retry-github-repo"></a>

有关此模式示例架构的完整实施，请参阅 GitHub 存储库 [https://github.com/aws-samples/retry-with-backoff](https://github.com/aws-samples/retry-with-backoff)。

## 相关内容
<a name="retry-backoff-resources"></a>
+ [超时、重试和抖动回退](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/)（Amazon Builders' Library）