項目進行微服務化之后,隨之而來的問題就是服務調用過程中發生錯誤、超時等問題的時候我們該怎么處理,比如因為網絡的瞬時問題導致服務超時,這在我本人所在公司的項目里是很常見的問題,當發生請求超時問題的時候,我們希望能夠自動重試,或者是在發生服務錯誤時采取一定的策略,比如限流熔斷等等。
本篇將會使用Polly處理服務調用過程中發生的超時問題。
打開我們的MI.Web項目,通過NuGet引用 Microsoft.Extensions.Http 和 Microsoft.Extensions.Http.Polly。
在Startup中添加如下代碼:
public static class ServiceCollectionExtensions { public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) {
//依賴注入 services.AddSingleton<IApiHelperService, ApiHelperService>(); services.AddSingleton<IAccountService, AccountService>(); services.AddSingleton<IPictureService, PictureService>(); services.AddOptions(); services.AddMvc(options => { options.Filters.Add<HttpGlobalExceptionFilter>(); }); services.AddMemoryCache(); services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); }); return services; } public static IServiceCollection AddHttpServices(this IServiceCollection services) {//注冊http服務 //services.AddHttpClient(); services.AddHttpClient("MI") .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuiBreakerPolicy()); return services; } /// <summary> /// 重試策略 /// </summary> public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); } /// <summary> /// 熔斷策略 /// </summary> private static IAsyncPolicy<HttpResponseMessage> GetCircuiBreakerPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)); } }
這里我們通過Polly分別配置了重試和熔斷的策略,當發生404、500、408(超時)問題的時候會重試6次,間隔時間2秒;熔斷策略是如果有5個請求發生500或者超時則開啟熔斷,時間是30秒,Polly可以配置非常詳細的策略,以后有時間再專門介紹(其實是我現在不會。。。對不起)。因為這里Polly是結合HttpClientFactory來使用的,所以我們需要使用上面的代碼:
services.AddHttpClient("MI") .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuiBreakerPolicy());
這里可以理解為我們創建了一個名稱為“MI”的HttpClientFactory,然后為其配置了重試和熔斷策略,這里順帶提一句是,HttpClientFactory是在.net core 2.1中加入的,它解決了之前HttpClient的資源釋放不及時的痛點,之前使用HttpClient時我們需要使用using或者創建靜態變量,前者的問題是頻繁的創建和銷毀帶來的資源損耗,不僅僅和對象資源,因為HttpClient還涉及到網絡資源,后者則會導致資源釋放不及時,靜態資源如果不進行處理會一直存在,而HttpClientFactory內部會緩存連接資源,同時會在不使用后的一段間隔時間后進行銷毀,同時內存會維護一個隊列,單例。
添加完上面這些后我們還需要在ConfigureServices方法中進行注冊:
public void ConfigureServices(IServiceCollection services) { services.AddCustomMvc(Configuration).AddHttpServices(); }
我為API的調用封裝了一個接口層:
public interface IApiHelperService { Task<T> PostAsync<T>(string url, IRequest request); Task<T> GetAsync<T>(string url); }
public class ApiHelperService : IApiHelperService { private readonly IHttpClientFactory _httpClientFactory; private readonly IMemoryCache cache; private readonly ILogger<ApiHelperService> _logger; public ApiHelperService(IMemoryCache cache, ILogger<ApiHelperService> _logger, IHttpClientFactory _httpClientFactory) { this._httpClientFactory = _httpClientFactory; this.cache = cache; this._logger = _logger; } /// <summary> /// HttpClient實現Post請求 /// </summary> public async Task<T> PostAsync<T>(string url, IRequest request) { var http = _httpClientFactory.CreateClient("MI"); //添加Token var token = await GetToken(); http.SetBearerToken(token); //使用FormUrlEncodedContent做HttpContent var httpContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); //await異步等待回應 var response = await http.PostAsync(url, httpContent); //確保HTTP成功狀態值 response.EnsureSuccessStatusCode(); //await異步讀取 string Result = await response.Content.ReadAsStringAsync(); var Item = JsonConvert.DeserializeObject<T>(Result); return Item; } }
圖中標紅的部分就是使用帶有Polly策略的IHttpClientFactory來創建HttpClient,然后進行Post調用,Get調用也是同樣的。
然后我們啟動Web項目,開啟控制台模式進行日志查看,訪問登錄功能:
我們可以看到,一共訪問了登錄方法兩次,第一次發生了404錯誤,接着自動又請求了一次,成功。
這里只是做一次演示,接下來會在Ocelot網關中接入Polly,這樣可以避免在每個項目里都進行這樣的配置,當然如果項目里有功能需要進行特許的策略配置,是可以采用這種方式的。