allbet官网:NetCore“项目实”战篇07---{服务珍爱之}polly

2020-05-21 7 views 0

扫一扫用手机浏览

 

1、  为什么要用polly

‘前面的项目’中,(一个)‘服务’<挪用>另(一个)(Zhengwei.Identity<挪用>Zhengwei.Use.Api)‘服务’「时」是直接<挪用>的,在【这】个<挪用>的过程中可能会发生种种瞬态故障,‘这里的说的瞬态故障〖包〗含了程’序发生的〖异常〗和泛起不符合开发者预期「的效果」。所谓瞬态故障,《就是说故障不是必然会发生的》,<而是有「时」可能>会发生的,(好比)网络偶然会突然泛起不稳定或无法访问这种故障。Polly【对于】这些故障会有自己的处置计谋

2、  Polly 的七种计谋

  1. 《重试》:泛起故障自动《重试》。
  2. “熔断”:(当系统遇到严重)(问题)「时」,‘快速回馈失败比让用户’/<挪用>者守候要好,『限制系统失足的体量』,<有助于系统恢复>。(好比),当我们去调(一个)第三方的 API,有很长一段「时」间 API ‘都没有响应’,可能对方‘服务’器瘫痪了。若是我们的系统还不停地《重试》,「不仅会加重系统的肩负」, 还会可能导致系统其它义务受影[响。以是,当系统失足的次数跨越了指定的阈值,就要中止当前线路,守候一段「时」间后再继续。
  3. 超「时」:当系统跨越一定「时」间的守候,我们就险些可以判断不可能会有乐成「的效果」。(好比)平「时」(一个)网络【请求】瞬间就完成了,“若是有一次”网络【请求】跨越了 30 秒还没完成,我们就知道这次大概率是不会返回乐成「的效果」了。因此,我们需要设置系统的超「时」「时」间,制止系统长「时」间做无谓的守候。
  4. 隔离:当系统的一处泛起故障「时」,可能促发多个失败的<挪用>,很容易耗尽主机的资源(如 CPU)。下游系统泛起故障可能导致上游的故障的<挪用>,甚至可能蔓延到导致系统溃逃。以是要将可控的操作限制在(一个)牢固巨细的资源池中,《以隔离有潜在可》能相互影响的操作。
  5. 回退:【有些错误无法制止】,就要有备用的方案。【这】个就像浏览器不支持一些新的 CSS 特征就要分外引用(一个) polyfill 「一样」。「一样」平常情形,当无法制止的错误发生「时」,我们要有(一个)合理的返回来取代失败,(好比)很常见的(一个)场景是,当用户没有上传头像「时」,我们就给他(一个)默认头像
  6. 缓存:「一样」平常我们会把频仍使用且不会怎么转变的资源缓存起来,以提高系统的响应速度。若是纰谬缓存资源的<挪用>举行封装,那么我们<挪用>的「时」刻就要先判断缓存中有没有【这】个资源,【有的话就】从缓存返回,否则就从资源存储的地方((好比)数据库)获取后缓存起来,「再返回」,而且有「时」还要思量缓存过「时」和若何更新缓存的(问题)。Polly 提供了缓存计谋的支持,使得(问题)变得简朴
  7. 计谋〖包〗:一种操作会有多种差别的故障,而差别的故障处置需要差别的计谋。「这些差别的计谋必须〖包〗在一」起,作为(一个)计谋〖包〗,‘才气应用在同一种操作上’。(这就是文章开头说的) Polly 的弹性,即种种差别的计谋能够天真地组合起来。

3、  polly(在项目中集成)

polly主要做什么的领会后我们就最先在项目中举行集成吧。

a. 在解决方案中新建(一个)类库,(名叫):Resilience,引用NuGet〖包〗Pollyt和Newtonsoft.Json〖包〗。

b. 在该类库中建立接口IHttpClient.cs,接口中有三个<方式>, 【这】个接口的主要目的是为了替换之前[(Zhengwei.Identity‘服务’中UserService.cs类CheckOrCreate<方式>中在<挪用>Zhengwei.Use.Api‘服务’)使用的System.Net.Http. HttpClient““工具””,<代码如下>:

public interface IHttpClient
    {
        Task<HttpResponseMessage> PostAsync<T>(string url, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Beare");
        Task<HttpResponseMessage> DoPostPutAsync(HttpMethod method, string url, Func<HttpRequestMessage> requestMessageAction, string authorizationToken = null, string requestId = null, string authorizationMethod = "Beare");
        Task<HttpResponseMessage> PostAsync(string url, Dictionary<string, string> form, string authorizationToken = null, string requestId = null, string authorizationMethod = "Beare");
    }

c. 在该类库中新建类ResilienceHttpClient.cs‘来实’现IHttpClient.cs接口,<代码如下>

public class ResilienceHttpClient : IHttpClient
    {
        //凭据url origin去建立policy
        private HttpClient _httpClient;
        //把policy‘打’〖包〗成组合policy wraper,举行内陆缓存。
        private readonly Func<string, IEnumerable<Policy>> _policyCreator;
        private readonly ConcurrentDictionary<string, PolicyWrap> _policyWraps;
        private ILogger<ResilienceHttpClient> _logger;
        private IHttpContextAccessor _httpContextAccessor;

        public ResilienceHttpClient(Func<string, IEnumerable<Policy>> policyCreator,
            ILogger<ResilienceHttpClient> logger,
            IHttpContextAccessor httpContextAccessor)
        {
            _httpClient = new HttpClient();
            _policyWraps = new ConcurrentDictionary<string, PolicyWrap>();
            _policyCreator = policyCreator;
            _logger = logger;
            _httpContextAccessor = httpContextAccessor;
        }
        public async Task<HttpResponseMessage> PostAsync<T>(string url, T item, string authorizationToken=null, string requestId = null, string authorizationMethod = "Beare")
        {
            Func<HttpRequestMessage> func = () => CreateHttpRequestMessage(HttpMethod.Post, url, item);
            return await DoPostPutAsync(HttpMethod.Post, url, func, authorizationToken, requestId, authorizationMethod);
        }
        public async Task<HttpResponseMessage> PostAsync(string url, Dictionary<string, string> form, string authorizationToken=null, string requestId = null, string authorizationMethod = "Beare")
        {
            Func<HttpRequestMessage> func = () => CreateHttpRequestMessage(HttpMethod.Post, url, form);
            return await DoPostPutAsync(HttpMethod.Post, url,func, authorizationToken, requestId, authorizationMethod);
        }
        public Task<HttpResponseMessage> DoPostPutAsync(HttpMethod method,string url,Func<HttpRequestMessage> requestMessageAction,string authorizationToken=null,  string requestId = null, string authorizationMethod = "Beare")
        {
            if(method != HttpMethod.Post && method != HttpMethod.Put)
            {
                throw new ArgumentException("Value must be either post or put", nameof(method));
            }
            var origin = GetOriginFromUri(url);
            return HttpInvoker(origin,async () => {
                HttpRequestMessage requestMessage = requestMessageAction();

                    SetAuthorizationHeader(requestMessage);
                
                
                if (authorizationToken != null)
                {
                    requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(authorizationMethod, authorizationToken);
                }
                if(requestId != null)
                {
                    requestMessage.Headers.Add("x-requestid", requestId);
                }
                var response = await _httpClient.SendAsync(requestMessage);
                if(response.StatusCode == System.Net.HttpStatusCode.InternalServerError
                || response.StatusCode == System.Net.HttpStatusCode.BadRequest)
                {
                    throw new HttpRequestException();
                }
                return response;
            });

        }
        private HttpRequestMessage CreateHttpRequestMessage<T>(HttpMethod method,string url,T item)
        {
            var requestMessage = new HttpRequestMessage(method, url);
            requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json");
            return requestMessage;
        }
        private HttpRequestMessage CreateHttpRequestMessage<T>(HttpMethod method, string url, Dictionary<string, string> form)
        {
            var requestMessage = new HttpRequestMessage(method, url);
            requestMessage.Content = new FormUrlEncodedContent(form);
            return requestMessage;
        }
        private async Task<T> HttpInvoker<T>(string origin,Func<Task<T>> action)
        {
            var normalizedOrigin = NormalizeOrigin(origin);
            if(!_policyWraps.TryGetValue(normalizedOrigin,out PolicyWrap policyWrap))
            {
                policyWrap=Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());
                _policyWraps.TryAdd(normalizedOrigin, policyWrap);
            }
            return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin));
        }
        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 });
            }
        }

        
    }

《代码剖》析:

在这[个类的HttpInvoker()<方式>中我们建立了(一个)policyWrap““工具””,通过【这】个““工具””来执行我的http【请求】,这里执行的http【请求】实际上照样使用的我们System.Net.Http. HttpClient““工具””中的SendAsync()<方式>,“见”代码:var response = await _httpClient.SendAsync(requestMessage);可以理解为我们只是加入了我们的polly机制,将【请求】〖包〗装了一下。

随后对两将【请求】〖异常〗举行了处置:InternalServerError、BadRequest,并抛出HttpRequestException〖异常〗。

{在建立}policyWrap““工具””使用了如下的代码policyWrap=Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());

WrapAsync<方式>中实际上是要传入(一个)Policy的数组。【这】个policy数组是在<挪用>的<挪用>的「时」刻建立的,请看类ResilienceClientFactory .cs

d. 在使用polly『的项目中』,「也就是」Zhengwei.Identity『的项目中』来新建类ResilienceClientFactory.cs,我将他放在了新建的code〖文件夹中〗。“固然”在这之前我们要将NuGet〖包〗Polly引入进来,ResilienceClientFactory.cs<代码如下>:

public class ResilienceClientFactory
    {
        private ILogger<ResilienceHttpClient> _logger;
        private IHttpContextAccessor _httpContextAccessor;
        private int _retryCount;
        private int _exceptionCountBreaking;
        public ResilienceClientFactory(int exceptionCountBreaking,int retryCount,ILogger<ResilienceHttpClient> logger, IHttpContextAccessor httpContextAccessor)
        {
            _exceptionCountBreaking = exceptionCountBreaking;
            _retryCount = retryCount;
            _logger = logger;
            _httpContextAccessor = httpContextAccessor;
        }
        public ResilienceHttpClient GetResilienceHttpClient() =>
            new ResilienceHttpClient(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} 次《重试》"+
                    $"of{context.PolicyKey} "
                    +$"at {context.ExecutionKey}, "
                    +$"due to: {exception}";
                    _logger.LogWarning(msg);
                    _logger.LogDebug(msg);
                }),
                Policy.Handle<HttpRequestException>().CircuitBreakerAsync(
                    
                    _exceptionCountBreaking,
                    TimeSpan.FromMinutes(1),
                    (excption, duration) =>
                    {
                        _logger.LogTrace("“熔断”器打开");
                    },()=>{
                        _logger.LogTrace("“熔断”器关闭");
                    })
            };
        }
    }

《代码剖》析:

在【这】个类中我们看到了Policy““工具””数组的建立,(一个)““工具””实在就是一种计谋,我们界说了两种计谋,一是失足《重试》(WaitAndRetryAsync);二是超「时」“熔断”(CircuitBreakerAsync).在项目启动「时」我们会<挪用>GetResilienceHttpClient()<方式>,「也就是」new ResilienceHttpClient(origin =>CreatePolicy(origin), _logger,_httpContextAccessor)段代码,我们将policy““工具””数组传入到ResilienceHttpClient““工具””中,在【这】个““工具””中又建立了policyWrap““工具””,在<挪用>policyWrap““工具””的ExecuteAsync()<方式>。<方式>中需求传入我们的http【请求】,这样policy 所界说的几种计谋就和http【请求】产生了关联,至于若何关联的那就是polly【组件源码内可以看到的了】,【这里不深入解读】。{希望有机会给人人读下}polly的源码。

E. 在项目启动「时」注册并初始化ResilienceClientFactory““工具””,“并注册全局的”IhttpClient,<代码如下>:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentityServer()
                .AddExtensionGrantValidator<SmsAuthCodeValidator>()
                .AddDeveloperSigningCredential()
                .AddInMemoryClients(Config.GetClients())
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryApiResources(Config.GetApiResources());
            services.Configure<ServiceDisvoveryOptions>(Configuration.GetSection("ServiceDiscovery"));
            services.AddSingleton<IDnsQuery>(p =>
            {
                var s = p.GetRequiredService<IOptions<ServiceDisvoveryOptions>>().Value;
                return new LookupClient(s.Consul.DnsEndpoint.ToIpEndPoint());
            });
            //注册全局单例ResilienceClientFactory
            services.AddSingleton(typeof(ResilienceClientFactory), p =>
            {
                var logger = p.GetRequiredService<ILogger<ResilienceHttpClient>>();
                var httpcontextAccesser = p.GetRequiredService<IHttpContextAccessor>();
                var retryCount = 5;
                var exceptionCountAlloweBeforeBreaking = 5;
                return new ResilienceClientFactory(exceptionCountAlloweBeforeBreaking,retryCount,logger, httpcontextAccesser);
            });
            //services.AddSingleton(new HttpClient());
            //注册全局的IHttpClient
            services.AddSingleton<IHttpClient>(p=>
            {
                return p.GetRequiredService<ResilienceClientFactory>().GetResilienceHttpClient();
            }
                );

            services.AddScoped<IAuthCodeService, AuthCodeService>()
                .AddScoped<IUserService, UserService>();


            
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseIdentityServer();
            app.UseMvc();
        }
    }

F.『在项目中举行使用』

在之前的Zhengwei.Identity「项目中的」UserService.cs类中CheckOrCreate<方式>中我们在举行http【请求】是使用的是System.Net.Http. HttpClient““工具””。现在将其注掉,用使我用界说的IhttpClient““工具””,并<挪用>其中的<方式>PostAsync.<代码如下>:

public class UserService : IUserService
    {
        private ILogger<ResilienceHttpClient> _logger;
        //private string _userServiceUrl = "http://localhost:33545";
        private string _userServiceUrl;
        //private HttpClient _httpClient;
        private IHttpClient _httpClient;
        public UserService(IHttpClient httpClient
            ,IOptions<Dtos.ServiceDisvoveryOptions> serOp
            ,IDnsQuery dnsQuery
            , ILogger<ResilienceHttpClient> logger)
        {
            _logger = logger;
            _httpClient = httpClient;
            
            var address  = dnsQuery.ResolveService("service.consul",serOp.Value.ServiceName);
            var addressList = address.First().AddressList;
            var host = addressList.Any() ? addressList.First().ToString() : address.First().HostName;
            var port = address.First().Port;
            _userServiceUrl = $"http://{host}:{port}";

        }
        public async Task<int> CheckOrCreate(string phone)
        {
            var from = new Dictionary<string, string> { { "phone", phone } };
            // var content = new FormUrlEncodedContent(from);
            try
            {
                var response = await _httpClient.PostAsync(_userServiceUrl + "/api/users/check-or-create", from, null);
                if (response.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    var userId = await response.Content.ReadAsStringAsync();
                    int.TryParse(userId, out int intuserId);
                    return intuserId;
                }
            }
            catch (Exception ex)
            {

                _logger.LogError("checkorcreate 《重试》失败" + ex.Message + ex.StackTrace);
                throw ex;

            }
            
            return 0;

        }
    }

g.代码所有完成了,我们将【请求】的毗邻有意改成错语的,然后最先测试我们的polly 是否起作用了[,再将打开我们的postman。【请求】毗邻http://localhost:4157/connect/token。

在VS的输出控制台会看到如下的日志信息说明我们的polly失足《重试》计谋起了作用。

 

 

 

,

进入sunbet官网手机版登陆

欢迎进入sunbet官网手机版登陆!Sunbet 申博提供申博开户(sunbet开户)、SunbetAPP下载、Sunbet客户端下载、Sunbet『代理合作等业务』。

Sunbet内容转载自互联网,如有侵权,联系Sunbet删除。

本文链接地址:http://www.18hao-soso.com/post/1328.html

相关文章