netcore微服务Polly 实现熔断与降级机制
Polly的基本使用
Polly是一种.NET弹性和瞬态故障处理库,允许我们以非常顺畅和线程安全的方式来执诸如行重试,断路,超时,故障恢复等策略。 Polly针对对.NET 4.0,.NET 4.5和.NET Standard 1.1以及.NET Core实现,该项目作者现已成为.NET基金会一员,项目一直在不停迭代和更新,项目地址https://github.com/App-vNext/Polly
该库实现了七种恢复策略,下面我一一为您来介绍。
重试策略(Retry)
重试策略针对的前置条件是短暂的故障延迟且在短暂的延迟之后能够自我纠正。允许我们做的是能够自动配置重试机制。
断路器(Circuit-breaker)
断路器策略针对的前置条件是当系统繁忙时,快速响应失败总比让用户一直等待更好。保护系统故障免受过载,Polly可以帮其恢复。
超时(Timeout)
超时策略针对的前置条件是超过一定的等待时间,想要得到成功的结果是不可能的,保证调用者不必等待超时。
隔板隔离(Bulkhead Isolation)
隔板隔离针对的前置条件是当进程出现故障时,多个失败一直在主机中对资源(例如线程/ CPU)一直占用。下游系统故障也可能导致上游失败。这两个风险都将造成严重的后果。都说一粒老鼠子屎搅浑一锅粥,而Polly则将受管制的操作限制在固定的资源池中,免其他资源受其影响。
缓存(Cache)
缓存策略针对的前置条件是数据不会很频繁的进行更新,为了避免系统过载,首次加载数据时将响应数据进行缓存,如果缓存中存在则直接从缓存中读取。
回退(Fallback)
操作仍然会失败,也就是说当发生这样的事情时我们打算做什么。也就是说定义失败返回操作。
策略包装(PolicyWrap)
策略包装针对的前置条件是不同的故障需要不同的策略,也就意味着弹性灵活使用组合。
userapi添加项目Resilience
NuGet>Install-Package Polly
添加 interface IHttpClient
public interface IHttpClient { Task<HttpResponseMessage> PostAsync<T>(string uri, T item, string authorizationToken=null, string requestId = null, string authorizationMethod = null); Task<HttpResponseMessage> PostAsync(string uri, Dictionary<string,string> form, string authorizationToken=null, string requestId = null, string authorizationMethod = null); Task<string> GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer"); Task<HttpResponseMessage> PutAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer"); Task<HttpResponseMessage> DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer"); }
添加类 ResilientHttpClient
public class ResilientHttpClient : IHttpClient { private readonly HttpClient _client; //根据url origin创建policy private readonly Func<string, IEnumerable<Policy>> _policyCreator; //把policy打包成组合policy wraper,进行本地缓存 private readonly ConcurrentDictionary<string, PolicyWrap> _policyWrappers; private ILogger<ResilientHttpClient> _logger; private IHttpContextAccessor _httpContextAccessor; public ResilientHttpClient(Func<string, IEnumerable<Policy>> policyCreator, ILogger<ResilientHttpClient> logger, IHttpContextAccessor httpContextAccessor) { _client = new HttpClient(); _policyCreator = policyCreator; _policyWrappers = new ConcurrentDictionary<string, PolicyWrap>(); _logger = logger; _httpContextAccessor = httpContextAccessor; } public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item, string authorizationToken=null, string requestId = null, string authorizationMethod = "Bearer") { Func<HttpRequestMessage> requestMessage = () => CreateHttpRequestMessage(HttpMethod.Post, uri, item); return await DoPostPutAsync(HttpMethod.Post, uri, requestMessage, authorizationToken, requestId, authorizationMethod); } public async Task<HttpResponseMessage> PostAsync(string uri, Dictionary<string, string> form, string authorizationToken=null, string requestId = null, string authorizationMethod = null) { Func<HttpRequestMessage> requestMessage =()=>CreateHttpRequestMessage(HttpMethod.Post, uri, form); return await DoPostPutAsync(HttpMethod.Post,uri, requestMessage, authorizationToken, requestId, authorizationMethod); } public Task<HttpResponseMessage> PutAsync<T>(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") { Func<HttpRequestMessage> requestMessage = () => CreateHttpRequestMessage(HttpMethod.Put, uri, item); return DoPostPutAsync(HttpMethod.Put, uri, requestMessage, authorizationToken, requestId, authorizationMethod); } public Task<string> GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer") { var origin = GetOriginFromUri(uri); return HttpInvoker(origin, async () => { var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri); if (authorizationToken != null) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); } var response = await _client.SendAsync(requestMessage); return await response.Content.ReadAsStringAsync(); }); } public Task<HttpResponseMessage> DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") { var origin = GetOriginFromUri(uri); return HttpInvoker(origin, async () => { var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri); if (authorizationToken != null) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); } if (requestId != null) { requestMessage.Headers.Add("x-requestid", requestId); } return await _client.SendAsync(requestMessage); }); } private Task<HttpResponseMessage> DoPostPutAsync(HttpMethod method,string uri,Func<HttpRequestMessage>requestMessageAction, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") { if (method != HttpMethod.Post && method != HttpMethod.Put) { throw new ArgumentException("Value must be either post or put.", nameof(method)); } //每次重试都必须创建新的stringcontent //每次通讯后都会处理 var origin = GetOriginFromUri(uri); return HttpInvoker(origin, async () => { HttpRequestMessage requestMessage = requestMessageAction(); SetAuthorizationHeader(requestMessage); if (authorizationToken != null) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); } if (requestId != null) { requestMessage.Headers.Add("x-requestid", requestId); } var response = await _client.SendAsync(requestMessage); // 如果httpresponsecode 500,则引发异常 // 断路器跟踪故障所需 if (response.StatusCode == HttpStatusCode.InternalServerError) { throw new HttpRequestException(); } return response; }); } private async Task<T> HttpInvoker<T>(string origin, Func<Task<T>> action) { var normalizedOrigin = NormalizeOrigin(origin); if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap)) { policyWrap = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray()); _policyWrappers.TryAdd(normalizedOrigin, policyWrap); } // 执行应用全部的操作 // 包装器中定义的策略 return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin)); } private HttpRequestMessage CreateHttpRequestMessage<T>(HttpMethod method,string url, T item) { return new HttpRequestMessage(method, url) { Content = new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json") }; } private HttpRequestMessage CreateHttpRequestMessage(HttpMethod method, string url, Dictionary<string, string> form) { return new HttpRequestMessage(method, url) { Content = new FormUrlEncodedContent(form) }; } private static string NormalizeOrigin(string origin) { return origin?.Trim().ToLower(); } private static string GetOriginFromUri(string uri) { var url = new Uri(uri); var origin = $"{url.Scheme}://{url.DnsSafeHost}:{url.Port}"; return origin; } private void SetAuthorizationHeader(HttpRequestMessage requestMessage) { var authorizationHeader = _httpContextAccessor.HttpContext.Request.Headers["Authorization"]; if (!string.IsNullOrEmpty(authorizationHeader)) { requestMessage.Headers.Add("Authorization", new List<string>() { authorizationHeader }); } } }
User.Identity 项目添加文件夹Infrastructure
新建 ResilienceHttpClientFactory
public class ResilienceHttpClientFactory { private ILogger<ResilientHttpClient> _logger; private IHttpContextAccessor _httpContextAccessor; private int _retryCount; private int _exceptionCountAllowedBeforeBreacking; public ResilienceHttpClientFactory(ILogger<ResilientHttpClient> logger, IHttpContextAccessor httpContextAccessor, int retryCount,int exceptionCountAllowedBeforeBreacking) { _logger = logger; _httpContextAccessor = httpContextAccessor; _retryCount = retryCount; _exceptionCountAllowedBeforeBreacking = exceptionCountAllowedBeforeBreacking; } public ResilientHttpClient GetResilienceHttpClient() { return new ResilientHttpClient(origin => CreatePolicy(origin), _logger, _httpContextAccessor); } private Policy[] CreatePolicy(string origin) { return new Policy[]{ Policy.Handle<HttpRequestException>().WaitAndRetryAsync( _retryCount, retryAttempt=>TimeSpan.FromSeconds(Math.Pow(2,retryAttempt)), (exception,timeSpan,retryCount,context)=> { var msg=$"第{retryCount}implemented with polly's retryPolicy"+ $"of {context.PolicyKey}"+ $"at {context.ExecutionKey}"+ $"due to:{exception}."; _logger.LogWarning(msg); _logger.LogDebug(msg); } ), Policy.Handle<HttpRequestException>().CircuitBreakerAsync(_exceptionCountAllowedBeforeBreacking,TimeSpan.FromMinutes(1), (excpeiton,duration)=>{ _logger.LogTrace("熔断器打开"); },()=> { _logger.LogTrace(""); }) }; } }
Startup 修改
ConfigureServices添加 服务注册
//注册全局单利 ResilienceHttpClientFactory services.AddSingleton(typeof(ResilienceHttpClientFactory), sp => { var logger = sp.GetRequiredService<ILogger<ResilientHttpClient>>(); var httpcontextAccesser = sp.GetRequiredService<IHttpContextAccessor>(); var retryCount = 5; var exceptionCountAllowedBeforeBreacking = 5; return new ResilienceHttpClientFactory(logger, httpcontextAccesser, retryCount, exceptionCountAllowedBeforeBreacking); }); //注册全局单利IHttpClient services.AddSingleton<IHttpClient>(sp => { return sp.GetRequiredService<ResilienceHttpClientFactory>().GetResilienceHttpClient(); });
UserService HttpClient修改为项目 Resilience IHttpClient
改造完毕运行项目
系统会默认记录允许的异常计数,和重试机制
正常运行
异常的时候重试
git : https://gitee.com/LIAOKUI/user.api
参考:https://github.com/HoussemDellai/ResilientHttpClient
https://github.com/App-vNext/Polly
https://blog.csdn.net/guwei9111986/article/details/51649240