Flurl使用Polly实现重试Policy
在使用Flurl作為HttpClient向Server請求時,由于網絡或者其它一些原因導致請求會有失敗的情況,比如HttpStatusCode.NotFound、HttpStatusCode.ServiceUnavailable、HttpStatusCode.RequestTimeout等;網絡上有比較多的HttpClientFactory使用Polly來實現重試的內容,奈何已經習慣使用Flurl的人,要全部換回到IHttpClient的確有不方便的地方,因為本文使用Flurl的Polly來實現重試機制做一個整理;
不使用Polly來測試
提供一個接口以便做請求測試
[Route("api/[controller]")] [ApiController] public?class?PollyController?:?ControllerBase {private?readonly?ILogger<PollyController>?_logger;public?PollyController(ILogger<PollyController>?logger){_logger?=?logger;}//?GET:?api/<PollyController>[HttpGet]public?IActionResult?Get(){var?random?=?new?Random().Next(0,?8);switch?(random){case?0:_logger.LogInformation("About?to?serve?a?404");return?StatusCode(StatusCodes.Status404NotFound);case?1:_logger.LogInformation("About?to?serve?a?503");return?StatusCode(StatusCodes.Status503ServiceUnavailable);case?2:_logger.LogInformation("Sleeping?for?10?seconds?then?serving?a?504");Thread.Sleep(10000);_logger.LogInformation("About?to?serve?a?504");return?StatusCode(StatusCodes.Status504GatewayTimeout);default:_logger.LogInformation("About?to?correctly?serve?a?200?response");return?Ok(new?{time?=?DateTime.Now.ToLocalTime()});}} }創建一個請求客戶端
public?class?HomeController?:?Controller {private?readonly?ILogger<HomeController>?_logger;public?HomeController(ILogger<HomeController>?logger){_logger?=?logger;}public?async?Task<IActionResult>?Index(){try{var?time?=?await?"http://127.0.0.1:5000/api/polly".GetJsonAsync();_logger.LogInformation($"App:?success?-?{time.time}");return?View(time.time);}catch?(Exception?e){_logger.LogWarning($"App:?failed?-?{e.Message}");throw;}} }嘗試請求,可以發現有很多請求失敗的地方,這個情況很不理想,服務器有較大的機率不能正常的響應
info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?serve?a?404 info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?correctly?serve?a?200?response info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?correctly?serve?a?200?response info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?correctly?serve?a?200?response info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?serve?a?503 info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?serve?a?503 info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?correctly?serve?a?200?response info:?SuppertRcsInterfaceTest.Controllers.PollyController[0]About?to?serve?a?404 ?針對這個情況有沒有什么解決的辦法呢,答案是肯定的,粗暴的想法就是失敗了再重新做請求,直接在Flurl的返回結果中做這個邏輯處理會比較麻煩也不方便統一的管理,如此就找到了Polly
使用Polly來測試
首先安裝Polly, Install-Package Polly
下面先給出Polly的簡單介紹后接著給出Policy的代碼片段
?Polly的七種策略:重試、斷路、超時、隔離、回退和緩存策略,本文使用到了重試、超時策略
重試(Retry):出現故障自動重試,這個是常見的場景
斷路(Circuit-breaker):當系統遇到嚴重的問題時,快速回饋失敗比讓用戶/調用者等待要好,限制系統出錯的消耗,有助于系統恢復,比如,當我們去調用一個第三方的API,有很長一段時間API都沒有響應,可能對方服務器癱瘓了,如果我們的系統還不停地重試,不僅會加重系統的負擔,還有可能導致系統其它任務受影響,因此,當系統出錯的次數超過了指定的閾值,就得中斷當前線程,等待一段時間后再繼續;比如: Policy.Handle<SomeException>().CircuitBreaker(2, TimeSpan.FromMinutes(1));表示當系統出現兩次某個異常時就停下來,等待1分鐘后再繼續,還可以在斷路時定義中斷的回調和重啟的回調。
超時(Timeout):當系統超過一定時間的等待,就可以判斷不可能會有成功的結果;比如平時一個網絡請求瞬間就完成了,如果有一次網絡請求超過了30秒還沒有完成,我們就可以判定不可能會返回成功的結果了,因此,我們需要設置系統的超時時間,避免系統長時間無謂的等待;比如:Policy.Timeout(30, (context, span, task) => {// do something});表示設置了超時時間不能超過30秒,否則就認為是錯誤的結果,并執行回調。
隔離(Bulkhead Isolation):當系統的一處出現故障時,可能觸發多個失敗的調用,對資源有較大的消耗,下游系統出現故障可能導致上游的故障的調用,甚至可能蔓延到導致系統崩潰,所以要將可控的操作限制在一個固定大小的資源池中,以隔離有潛在可能相互影響的操作;比如:Policy.Bulkhead(12, context => {// do something});表示最多允許12個線程并發執行,如果執行被拒絕,則執行回調。
回退(Fallback):有些錯誤無法避免,就要有備用的方案,當無法避免的錯誤發生時,我們要有一個合理的返回來代替失敗;比如:Policy.Handle<Whatever>().Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar());表示當用戶沒有上傳頭像時,我們就給他一個默認頭像。
緩存(Cache):一般我們會把頻繁使用且不會怎么變化的資源緩存起來,以提高系統的響應速度,如果不對緩存資源的調用進行封裝,那么我們調用的時候就要先判斷緩存中有沒有這個資源,有的話就從緩存返回,否則就從資源存儲的地方獲取后緩存起來再返回,而且有時還要考慮緩存過期和如何更新緩存的問題;Polly提供了緩存策略的支持,使得問題變得簡單。
策略包(Policy Wrap):一種操作會有多種不同的故障,而不同的故障處理需要不同的策略,這些不同的策略必須包在一起,作為一個策略包,才能應用在同一種操作上,這就是Polly的彈性特性,即各種不同的策略能夠靈活地組合起來
更多...
using?System; using?System.Linq; using?System.Net; using?System.Net.Http; using?System.Threading; using?System.Threading.Tasks; using?Flurl.Http.Configuration; using?Microsoft.Extensions.Logging; using?Polly; using?Polly.Retry; using?Polly.Timeout; using?Polly.Wrap;namespace?WithPollyClient.Services {public?class?Policies{private?readonly?ILogger<Policies>?_logger;public?Policies(ILogger<Policies>?logger){_logger?=?logger;}private?AsyncTimeoutPolicy<HttpResponseMessage>?TimeoutPolicy{get{return?Policy.TimeoutAsync<HttpResponseMessage>(3,?(context,?span,?task)?=>{_logger.LogInformation($"Policy:?Timeout?delegate?fired?after?{span.Seconds}?seconds");return?Task.CompletedTask;});}}private?AsyncRetryPolicy<HttpResponseMessage>?RetryPolicy{get{HttpStatusCode[]?retryStatus?={HttpStatusCode.NotFound,HttpStatusCode.ServiceUnavailable,HttpStatusCode.RequestTimeout};return?Policy.HandleResult<HttpResponseMessage>(r?=>?retryStatus.Contains(r.StatusCode)).Or<TimeoutRejectedException>().WaitAndRetryAsync(new[]{//?表示重試3次,第一次1秒后重試,第二次2秒后重試,第三次4秒后重試TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4)},?(result,?span,?count,?context)?=>{_logger.LogInformation($"Policy:?Retry?delegate?fired,?attempt?{count}");});}}public?AsyncPolicyWrap<HttpResponseMessage>?PolicyStrategy?=>Policy.WrapAsync(RetryPolicy,?TimeoutPolicy);}public?class?PolicyHandler?:?DelegatingHandler{private?readonly?Policies?_policies;public?PolicyHandler(Policies?policies){_policies?=?policies;}protected?override?Task<HttpResponseMessage>?SendAsync(HttpRequestMessage?request,?CancellationToken?cancellationToken){return?_policies.PolicyStrategy.ExecuteAsync(ct?=>?base.SendAsync(request,?ct),?cancellationToken);}}public?class?PollyHttpClientFactory?:?DefaultHttpClientFactory{private?readonly?Policies?_policies;public?PollyHttpClientFactory(Policies?policies){_policies?=?policies;}public?override?HttpMessageHandler?CreateMessageHandler(){return?new?PolicyHandler(_policies){InnerHandler?=?base.CreateMessageHandler()};}} }接下來在Starup中對Flurl進行配置
public?void?ConfigureServices(IServiceCollection?services) {services.AddControllersWithViews();services.AddSingleton<Policies>(); }//?This?method?gets?called?by?the?runtime.?Use?this?method?to?configure?the?HTTP?request?pipeline. public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env) {var?policies?=?app.ApplicationServices.GetService<Policies>();FlurlHttp.Configure(setting?=>setting.HttpClientFactory?=?new?PollyHttpClientFactory(policies));......再次嘗試請求,可以看到結果非常之理想
WithPollyClient.Services.Policies:?Information:?Policy:?Retry?delegate?fired,?attempt?1 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:14 WithPollyClient.Services.Policies:?Information:?Policy:?Retry?delegate?fired,?attempt?1 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:17 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:22 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:23 WithPollyClient.Services.Policies:?Information:?Policy:?Retry?delegate?fired,?attempt?1 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:25 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:31 WithPollyClient.Services.Policies:?Information:?Policy:?Retry?delegate?fired,?attempt?1 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:34 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:39 WithPollyClient.Services.Policies:?Information:?Policy:?Retry?delegate?fired,?attempt?1 WithPollyClient.Services.Policies:?Information:?Policy:?Timeout?delegate?fired?after?3?seconds WithPollyClient.Services.Policies:?Information:?Policy:?Retry?delegate?fired,?attempt?2 WithPollyClient.Controllers.HomeController:?Information:?App:?success?-?2021/3/14?16:50:46富客戶端中使用的情況
?有時候呢,例如在WPF或者是其它的富客戶端上面也會經常使用到Flurl的情況,如下
var?time?=?await?Policy.Handle<FlurlHttpException>().OrResult<IFlurlResponse>(r?=>?!r.ResponseMessage.IsSuccessStatusCode).WaitAndRetryAsync(new[]{TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4)},?(result,?span,?count,?context)?=>{_logger.LogInformation(count.ToString());}).ExecuteAsync(()?=>?"http://127.0.0.1:5000/api/polly".WithTimeout(3).GetAsync()).ReceiveJson();_logger.LogInformation($"App:?success?-?{time.time}"); return?View(time.time);?
時間如流水,只能流去不流回。
公眾號:Dotnet9
號主微信號:dotnet9
作者:非法關鍵字
原文:Flurl使用Polly添加重試機制
編輯:沙漠之盡頭的狼
日期:2021-03-15
總結
以上是生活随笔為你收集整理的Flurl使用Polly实现重试Policy的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 315曝光不良奸商 对企业不能罚酒三杯
- 下一篇: Dapr Meetup 3.22【周六】