一篇解決在 .NET 8中使用 Polly 處理瞬態(tài)故障
在本文中,我們將學(xué)習(xí)如何在與服務(wù)交互時(shí)實(shí)現(xiàn)重試機(jī)制,尤其當(dāng)服務(wù)出現(xiàn)一些瞬態(tài)故障時(shí)。
什么是瞬態(tài)故障?
瞬態(tài)故障是指持續(xù)時(shí)間較短的故障。例如:網(wǎng)絡(luò)連接因路由器重啟而中斷,服務(wù)因部署更新而短暫不可用,或資源耗盡導(dǎo)致連接被拒絕。
對(duì)于瞬態(tài)故障,故障持續(xù)時(shí)間通常很短,服務(wù)很快會(huì)恢復(fù)。因此,為了提高容錯(cuò)性,可以在失敗后重試多次,然后再接受失敗結(jié)果。
我們可以通過重試策略來處理瞬態(tài)故障,也就是不斷重新嘗試請(qǐng)求,直到成功或達(dá)到重試上限。
重試策略的配置選項(xiàng)
- 重試次數(shù):定義最大重試次數(shù)。
- 重試間隔時(shí)間:定義每次重試之間的時(shí)間間隔。
本文將介紹三種重試策略:
策略 1:立即重試 5 次
根據(jù)此策略,系統(tǒng)會(huì)連續(xù)重試 5 次請(qǐng)求,直到成功返回響應(yīng)。如果在 5 次重試后仍然失敗,則接受失敗結(jié)果。
圖片
策略 2:重試 5 次并等待 3 秒
根據(jù)此策略,系統(tǒng)在每次重試前等待 3 秒,然后再向響應(yīng)服務(wù)發(fā)出請(qǐng)求。
圖片
策略 3:指數(shù)回退重試 5 次
根據(jù)此策略,系統(tǒng)會(huì)在請(qǐng)求之間采用指數(shù)級(jí)等待時(shí)間,例如 1 秒、3 秒、5 秒、8 秒。
圖片
我們可以使用 Polly 實(shí)現(xiàn)這些重試機(jī)制,并通過基于類的配置實(shí)現(xiàn)。下面開始編碼實(shí)現(xiàn)。
創(chuàng)建響應(yīng)服務(wù)(Response Service)
首先創(chuàng)建一個(gè)新的 .NET Web API 應(yīng)用程序,命名為 Response Service。在 Program.cs 文件中添加控制器映射:
builder.Services.AddSwaggerGen();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
然后創(chuàng)建一個(gè) 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 函數(shù)實(shí)現(xiàn)了服務(wù)內(nèi)的瞬態(tài)故障。當(dāng)隨機(jī)生成的整數(shù)小于輸入的 ID 時(shí),有可能返回內(nèi)部服務(wù)器錯(cuò)誤。
運(yùn)行代碼并通過 Postman 測(cè)試。根據(jù)生成的隨機(jī)整數(shù),響應(yīng)服務(wù)的狀態(tài)碼會(huì)隨機(jī)返回 200 或 500。
圖片
創(chuàng)建請(qǐng)求服務(wù)(Request Service)
接下來,創(chuàng)建另一個(gè)新的 .NET Web API 應(yīng)用程序,命名為 Request Service。在 Program.cs 中同樣添加控制器到管道中。
創(chuàng)建 RequestController.cs 文件,用于通過 HttpClient 調(diào)用 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);
}
}
}
可以運(yùn)行請(qǐng)求服務(wù)并在 Postman 中驗(yàn)證。此時(shí)我們會(huì)從響應(yīng)服務(wù)中得到失敗消息,因?yàn)檫€未實(shí)現(xiàn)重試機(jī)制。
使用 Polly 實(shí)現(xiàn)重試機(jī)制
使用 dotnet cli 運(yùn)行以下命令將 Polly 包添加到請(qǐng)求服務(wù)中:
dotnet add package Microsoft.Extensions.Http.Polly
創(chuàng)建一個(gè)名為 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)));
}
}
}
在上述代碼中,我們?cè)跇?gòu)造函數(shù)中初始化了不同的重試策略。以 LinearHttpRetry 為例,若返回結(jié)果不是 SuccessStatusCode,則 WaitAndRetryAsync 方法會(huì)重試 5 次,每次間隔 3 秒。
接著,我們?cè)?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);
}
由于我們?cè)?Program.cs 中為命名的 Http 客戶端配置了策略,因此可以直接使用 IHttpClientFactory 來創(chuàng)建客戶端,并且策略已經(jīng)啟用。讓我們運(yùn)行代碼并在 Postman 中測(cè)試 LinearHttpRetry 策略。
在 Postman 中,我們成功地測(cè)試了線性等待策略。
圖片
從響應(yīng)服務(wù)的調(diào)試信息中可以看到,在獲得成功響應(yīng)之前經(jīng)歷了四次失敗。
圖片
在本文中,我們使用 Polly 實(shí)現(xiàn)了重試策略。除此之外,Polly 還提供其他模式,比如斷路器模式。
以上就是本文的全部?jī)?nèi)容,如有問題請(qǐng)留言。
譯文地址:c-sharpcorner.com/article/handling-transient-failures-in-net-8-with-polly