[ASP.NET Core] 筆記Polly的各種用法

筆記Polly的各種用法

nuget package

dotne add package Microsoft.Extensions.Http.Polly

Retry策略

1.Startup

  • 針對IHttpClientBuilder
    IHttpClientBuilder httpClientBuilder = services.AddHttpClient("Test");
    IHttpClientBuilder clientBuilder = services.AddHttpClient<MyService>();
  • 重試三次,分別在錯誤後等待1、5、10秒(總共會發出4次的request)
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddHttpClient("Test")
                    .AddTransientHttpErrorPolicy(builder =>
                    {
                        return builder.WaitAndRetryAsync(new[]
                        {
                            TimeSpan.FromSeconds(1),
                            TimeSpan.FromSeconds(5),
                            TimeSpan.FromSeconds(10),
                        });
                    });
        }

2.Controller

        [HttpGet]
        public async Task<HttpResponseMessage> Get()
        {
            var httpClient = _clientFactory.CreateClient("Test");
            return await httpClient.GetAsync("http://tasdasdxcsswweest.com");
        }

3.執行結果

4.Statup的寫法也可以改用這種,自定義規則,仍然有相同的執行結果

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            var policySelector = Policy.Handle<HttpRequestException>()
                                       .OrResult<HttpResponseMessage>(TestPredicate)
                                       .WaitAndRetryAsync(new[]
                                       {
                                           TimeSpan.FromSeconds(1),
                                           TimeSpan.FromSeconds(3),
                                           TimeSpan.FromSeconds(5),
                                       });
            services.AddHttpClient("Test")
                    .AddPolicyHandler(policySelector);
        }

Timeout

1.Startup

            var policySelector = Policy.TimeoutAsync<HttpResponseMessage>(1);
            services.AddHttpClient("Test")
                    .AddPolicyHandler(policySelector);

2.Polly的Timeout是包含了期間Retry以及等待的時間跟HttpClient的Timeout稍有不同


動態選擇策略

1.Startup

  • 若使用HttpGet,則使用重試策略,否則不做任何事
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            var policySelector = Policy.Handle<HttpRequestException>()
                                       .OrResult<HttpResponseMessage>(r => true)
                                       .WaitAndRetryAsync(new[]
                                       {
                                           TimeSpan.FromSeconds(1), 
                                           TimeSpan.FromSeconds(3), 
                                           TimeSpan.FromSeconds(5), 
                                       });
            var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>();
            services.AddHttpClient("Test")
                    .AddPolicyHandler(message => message.Method == HttpMethod.Get
                                          ? policySelector
                                          : noOpPolicy);
        }

註冊表

1.Startup

  • 事前註冊好各種策略
  • 後續加入HttpClient,並根據註冊的名稱指定策略
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            var keyValuePairs = new PolicyRegistry()
            {
                {
                    "Test", HttpPolicyExtensions.HandleTransientHttpError()
                                                .WaitAndRetryAsync(new[]
                                                {
                                                    TimeSpan.FromSeconds(1),
                                                    TimeSpan.FromSeconds(3),
                                                    TimeSpan.FromSeconds(5),
                                                })
                },
                {
                    "No", HttpPolicyExtensions.HandleTransientHttpError()
                                              .CircuitBreakerAsync(10,
                                                                   TimeSpan.FromSeconds(10))
                },
            };
            services.AddPolicyRegistry(keyValuePairs);

            services.AddHttpClient("Test")
                    .AddPolicyHandlerFromRegistry("Test")
                    .AddPolicyHandlerFromRegistry("No");
        }

斷路器

1.Startup

  • 當發生了一次錯誤,後續10秒內的請求都會中斷,直接返回exception
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddPolicyRegistry(new PolicyRegistry()
            {
                {
                    "No", HttpPolicyExtensions.HandleTransientHttpError()
                                              .CircuitBreakerAsync(1,
                                                                   TimeSpan.FromSeconds(10))
                },
            });

            services.AddHttpClient("Test")
                    .AddPolicyHandlerFromRegistry("No");
        }


隔板隔離策略

1.Startup

  • 第一個參數,指定每次允許的請求數量
  • 第二個參數,最大的排隊數量
  • 若超過第一及第二的設定數量,要做的事情
  • 被隔板隔離會拿到BulkheadRejectedException
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddPolicyRegistry(new PolicyRegistry()
            {
                {
                    "Test", Policy.BulkheadAsync<HttpResponseMessage>(1,
                                                                      2,
                                                                      context =>
                                                                      {
                                                                          return Task.CompletedTask;
                                                                      })
                },
            });

            services.AddHttpClient("Test")
                    .AddPolicyHandlerFromRegistry("Test");
        }

2.Controller

        [HttpGet]
        public HttpResponseMessage Get()
        {
            Parallel.For(1, 10, i =>
            {
                var httpClient = _clientFactory.CreateClient("Test");
                httpClient.PostAsync("http://tasdasdxcsswweest.com", null)
                          .GetAwaiter()
                          .GetResult();
            });
            return null;
        }

3.Result


Cache

1.Controller

  • 指定快取的Key
  • 需要自行呼叫EnsureSuccessStatusCode檢查是否為HttpStatusCode.OK
  • 若要在這層做快取,建議先思考是否適合
        [HttpGet]
        public async Task<HttpResponseMessage> Get()
        {
            var httpClient = _clientFactory.CreateClient("Test");
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://www.google.com");
            httpRequestMessage.SetPolicyExecutionContext(new Polly.Context("8888"));
            return await httpClient.SendAsync(httpRequestMessage);
        }

2.Polly.Context有提供一個Dictionary來傳遞資料,若有在Startup各個策略中的Hook加上行為,可以透過這個Dictionary取得資料

        // Controller
        [HttpGet]
        public async Task<HttpResponseMessage> Get()
        {
            var context = new Polly.Context("8888");
            context["TestValue"] = "test";
            
            var httpClient = _clientFactory.CreateClient("Test");
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://www.gooasdasdasdgle.com");
            httpRequestMessage.SetPolicyExecutionContext(context);
            return await httpClient.SendAsync(httpRequestMessage);
        }
        // Startup
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            var retryPolicy = HttpPolicyExtensions
                              .HandleTransientHttpError()
                              .WaitAndRetryAsync(new[]
                                                 {
                                                     TimeSpan.FromSeconds(1),
                                                     TimeSpan.FromSeconds(5),
                                                     TimeSpan.FromSeconds(10)
                                                 },
                                                 onRetryAsync: async (outcome, timespan, retryCount, context) =>
                                                 {
                                                     Console.WriteLine(context["TestValue"]);
                                                 });
            services.AddHttpClient("Test")
                    .AddPolicyHandler(retryPolicy);
        }

3.若需要在Hook中取得DI容器內的Service

        // Startup
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddHttpClient("Test")
                    .AddPolicyHandler((provider, message) =>
                                          HttpPolicyExtensions
                                              .HandleTransientHttpError()
                                              .WaitAndRetryAsync(new[]
                                                                 {
                                                                     TimeSpan.FromSeconds(1),
                                                                 },
                                                                 (outcome, timespan, retryCount, context) =>
                                                                 {
                                                                     var logger = provider.GetService<ILogger<HttpClient>>();
                                                                     logger.LogError("Error");
                                                                 }));
        }

早早就聽過Polly這套library,不過一直沒有機會用到它

這次挑了點時間把玩了一輪,筆記一下各個策略的用法


Sample Code https://github.com/ianChen806/PollySample