在本文中,我们将学习如何在与服务交互时实现重试机制,尤其当服务出现一些瞬态故障时。
什么是瞬态故障?
瞬态故障是指持续时间较短的故障。例如:网络连接因路由器重启而中断,服务因部署更新而短暂不可用,或资源耗尽导致连接被拒绝。
对于瞬态故障,故障持续时间通常很短,服务很快会恢复。因此,为了提高容错性,可以在失败后重试多次,然后再接受失败结果。
我们可以通过重试策略来处理瞬态故障,也就是不断重新尝试请求,直到成功或达到重试上限。
重试策略的配置选项
- 重试次数:定义最大重试次数。
- 重试间隔时间:定义每次重试之间的时间间隔。
本文将介绍三种重试策略:
策略 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()
{
}
[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