精:在 .NET 8中使用 Polly 处理瞬态故障

科技   2024-11-14 07:16   北京  

在本文中,我们将学习如何在与服务交互时实现重试机制,尤其当服务出现一些瞬态故障时。

什么是瞬态故障?

瞬态故障是指持续时间较短的故障。例如:网络连接因路由器重启而中断,服务因部署更新而短暂不可用,或资源耗尽导致连接被拒绝。

对于瞬态故障,故障持续时间通常很短,服务很快会恢复。因此,为了提高容错性,可以在失败后重试多次,然后再接受失败结果。

我们可以通过重试策略来处理瞬态故障,也就是不断重新尝试请求,直到成功或达到重试上限。

重试策略的配置选项

  • 重试次数:定义最大重试次数。
  • 重试间隔时间:定义每次重试之间的时间间隔。

本文将介绍三种重试策略:

策略 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(1101);

        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 RequestControllerControllerBase
    {
        public RequestController()
        {
        }
        [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 还提供其他模式,比如断路器模式。

以上就是本文的全部内容,如有问题请留言。

译文地址:c-sharpcorner.com/article/handling-transient-failures-in-net-8-with-polly


关注公众号DotNet开发跳槽    

DotNet开发跳槽
本公众号专注为.net开发工程师提供一个学习技术及求职/跳槽的交流平台。不定期分享NET技术类文章、面试题、求助技巧等干货,原创文章300+篇,让.net开发工程师学习/面试不再迷茫。ps: 后台回复“跳槽”,免费领取.NET开发面试题!
 最新文章