• 如何在与服务交互时实现重试机制?
  • 发布于 1周前
  • 51 热度
    0 评论
在本文中,我们将学习如何在与服务交互时实现重试机制,尤其当服务出现一些瞬态故障时。

什么是瞬态故障?
瞬态故障是指持续时间较短的故障。例如:网络连接因路由器重启而中断,服务因部署更新而短暂不可用,或资源耗尽导致连接被拒绝。对于瞬态故障,故障持续时间通常很短,服务很快会恢复。因此,为了提高容错性,可以在失败后重试多次,然后再接受失败结果。我们可以通过重试策略来处理瞬态故障,也就是不断重新尝试请求,直到成功或达到重试上限。

重试策略的配置选项
重试次数:定义最大重试次数。

重试间隔时间:定义每次重试之间的时间间隔。


本文将介绍三种重试策略:
策略 1:立即重试 5 次
根据此策略,系统会连续重试 5 次请求,直到成功返回响应。如果在 5 次重试后仍然失败,则接受失败结果。

策略 2:重试 5 次并等待 3 秒
根据此策略,系统在每次重试前等待 3 秒,然后再向响应服务发出请求。

策略 3:指数回退重试 5 次
根据此策略,系统会在请求之间采用指数级等待时间,例如 1 秒、3 秒、5 秒、8 秒。

我们可以使用 Polly 实现这些重试机制,并通过基于类的配置实现。下面开始编码实现。

创建响应服务(Response Service)
首先创建一个新的 .NET Web API 应用程序,命名为 Response Service。在 Program.cs 文件中添加控制器映射:
builder.Services.AddSwaggerGen();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
然后创建一个 ResponseController.cs 文件,添加如下操作方法:
[Route("api/[Controller]")]
[ApiController]
public class ResponseController : ControllerBase
{
    [HttpGet]
    [Route("{id:int}")]
    public ActionResult GetAResponse(int id)
    {
        Random rnd = new Random();
        var rndInteger = rnd.Next(1, 101);

        if (rndInteger >= id)
        {
            Console.WriteLine("Failure - Generate an Internal Error");
            return StatusCode(StatusCodes.Status500InternalServerError);
        }

        Console.WriteLine("Failure - Generated a Success");
        return Ok("Success");
    }
}
在上述代码中,我们使用 Random 函数实现了服务内的瞬态故障。当随机生成的整数小于输入的 ID 时,有可能返回内部服务器错误。运行代码并通过 Postman 测试。根据生成的随机整数,响应服务的状态码会随机返回 200 或 500。

创建请求服务(Request Service)
接下来,创建另一个新的 .NET Web API 应用程序,命名为 Request Service。在 Program.cs 中同样添加控制器到管道中。创建 RequestController.cs 文件,用于通过 HttpClient 调用 API,代码如下:
namespace RequestService.Controllers
{
    [ApiController]
    [Route("api/[Controller]")]
    public class RequestController: ControllerBase
    {
        public RequestController()
        {
            // 堆代码 duidaima.com
        }
        [HttpGet]
        public async Task<ActionResult> MakeRequest()
        {
           var client = new HttpClient();
            var response = await client.GetAsync("http://localhost:5202/api/response/25");
             var response = await _clientPolicy.LinearHttpRetry.ExecuteAsync( () =>
             client.GetAsync("http://localhost:5202/api/response/25")
             );
            if(response.IsSuccessStatusCode)
            {
               Console.WriteLine("--> Response Service Retuned Success");
               return Ok();
            }
            Console.WriteLine("--> Response Service Retuned Failure");
            return StatusCode(StatusCodes.Status500InternalServerError);
        }
    }
}
可以运行请求服务并在 Postman 中验证。此时我们会从响应服务中得到失败消息,因为还未实现重试机制。

使用 Polly 实现重试机制
使用 dotnet cli 运行以下命令将 Polly 包添加到请求服务中:
dotnet add package Microsoft.Extensions.Http.Polly
创建一个名为 Policies 的文件夹,并添加 ClientPolicy 类文件,代码如下:
using Polly;
using Polly.Retry;

namespace RequestService.Policies
{
    public class ClientPolicy
    {
        public AsyncRetryPolicy<HttpResponseMessage> ImmediateHttpRetry {get;}
        public AsyncRetryPolicy<HttpResponseMessage> LinearHttpRetry  {get;}
        public AsyncRetryPolicy<HttpResponseMessage> ExponentialHttpRetry  {get;}

        public ClientPolicy()
        {
            ImmediateHttpRetry = Policy.HandleResult<HttpResponseMessage>
            ( res => !res.IsSuccessStatusCode).RetryAsync(5);

            LinearHttpRetry = Policy.HandleResult<HttpResponseMessage>
            ( res => !res.IsSuccessStatusCode).
            WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds(3));

            ExponentialHttpRetry = Policy.HandleResult<HttpResponseMessage>
            ( res => !res.IsSuccessStatusCode).
            WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2,retryAttempt)));
        }
    }
}
在上述代码中,我们在构造函数中初始化了不同的重试策略。以 LinearHttpRetry 为例,若返回结果不是 SuccessStatusCode,则 WaitAndRetryAsync 方法会重试 5 次,每次间隔 3 秒。接着,我们在 Program.cs 中通过依赖注入配置 ClientPolicy:
builder.Services.AddSingleton<ClientPolicy> (new ClientPolicy());
然后在 RequestController 中使用 ClientPolicy:
private readonly ClientPolicy _clientPolicy;
private readonly IHttpClientFactory _clientFactory;

public RequestController(ClientPolicy clientPolicy, IHttpClientFactory clientFactory)
{
    _clientPolicy = clientPolicy;
    _clientFactory = clientFactory;
}

[HttpGet]
public async Task<ActionResult> MakeRequestNormalHttpClient()
{
    var client = new HttpClient();
    var response = await _clientPolicy.LinearHttpRetry.ExecuteAsync(() =>
        client.GetAsync("http://localhost:5202/api/response/25")
    );

    if(response.IsSuccessStatusCode)
    {
        Console.WriteLine("--> Response Service Retuned Success");
        return Ok();
    }
    Console.WriteLine("--> Response Service Retuned Failure");
    return StatusCode(StatusCodes.Status500InternalServerError);
}
为了更好地管理策略,可以在 Program.cs 中为命名的 HttpClient 添加策略:
builder.Services.AddHttpClient("Test")
    .AddPolicyHandler(request =>
        request.Method == HttpMethod.Get ?
        new ClientPolicy().LinearHttpRetry :
        new ClientPolicy().LinearHttpRetry
    );
在控制器中使用命名的 HttpClient:
public async Task<ActionResult> MakeRequest()
{
    var client = _clientFactory.CreateClient("Test");
    var response = await client.GetAsync("http://localhost:5202/api/response/25");

    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine("--> Response Service Returned Success");
        return Ok();
    }
    Console.WriteLine("--> Response Service Returned Failure");
    return StatusCode(StatusCodes.Status500InternalServerError);
}
由于我们在 Program.cs 中为命名的 Http 客户端配置了策略,因此可以直接使用 IHttpClientFactory 来创建客户端,并且策略已经启用。让我们运行代码并在 Postman 中测试 LinearHttpRetry 策略。在 Postman 中,我们成功地测试了线性等待策略。

从响应服务的调试信息中可以看到,在获得成功响应之前经历了四次失败。

在本文中,我们使用 Polly 实现了重试策略。除此之外,Polly 还提供其他模式,比如断路器模式。
以上就是本文的全部内容,如有问题请留言。
用户评论