9.Polly在NET中的使用,重试、熔断、超时、降级、限流简单用法
Polly 是一个 .NET 弹性和瞬态故障处理库,允许开发人员以 Fluent 和线程安全的方式来实现重试、断路、超时、隔离、限流和降级策略。
文档: https://gitee.com/hubo/Polly
重试(Retry)
出现故障自动重试,这个是很常见的场景,如:当发生请求异常、网络错误、服务暂时不可用时,就应该重试。许多故障都是短暂的,并且可能在短时间延迟后自我纠正(也许就只是昙花一现)。
熔断(Circuit-breaker)
当系统遇到严重问题时,快速回馈失败比让用户/调用者等待要好,限制系统出错的体量,有助于系统恢复。比如,当我们去调一个第三方的 API,有很长一段时间 API 都没有响应,可能对方服务器瘫痪了。如果我们的系统还不停地重试,不仅会加重系统的负担,还会可能导致系统其它任务受影响。所以,当系统出错的次数超过了指定的阈值,就要中断当前线路,等待一段时间后再继续。
超时(Timeout)
当系统超过一定时间的等待,我们就几乎可以判断不可能会有成功的结果。比如平时一个网络请求瞬间就完成了,如果有一次网络请求超过了 30 秒还没完成,我们就知道这次大概率是不会返回成功的结果了。因此,我们需要设置系统的超时时间,避免系统长时间做无谓的等待。
隔离(Bulkhead Isolation)
当系统的一处出现故障时,可能促发多个失败的调用,很容易耗尽主机的资源(如 CPU)。下游系统出现故障可能导致上游的故障的调用,甚至可能蔓延到导致系统崩溃。所以要将可控的操作限制在一个固定大小的资源池中,以隔离有潜在可能相互影响的操作。
降级(Fallback)
有些错误无法避免,就要有备用的方案。这个就像浏览器不支持一些新的 CSS 特性就要额外引用一个polyfill 一样。一般情况,当无法避免的错误发生时,我们要有一个合理的返回来代替失败。
缓存(Cache)
一般我们会把频繁使用且不会怎么变化的资源缓存起来,以提高系统的响应速度。如果不对缓存资源的调用进行封装,那么我们调用的时候就要先判断缓存中有没有这个资源,有的话就从缓存返回,否则就从资源存储的地方(比如数据库)获取后缓存起来,再返回,而且有时还要考虑缓存过期和如何更新缓存的问题。Polly 提供了缓存策略的支持,使得问题变得简单。
策略包(Policy Wrap)
一种操作会有多种不同的故障,而不同的故障处理需要不同的策略。这些不同的策略必须包在一起,作为一个策略包,才能应用在同一种操作上。这就是文章开头说的 Polly 的弹性,即各种不同的策略能够灵活地组合起来。
安装包:
Microsoft.Extensions.Http.Polly
创建两个项目,一个请求(Polly.Request:http://localhost:5034),一个响应(Polly.Response:http://localhost:5258)
第一步:
在Polly.Request项目中添加一个策略类
/// <summary> /// 所有的策略 /// </summary> public class ClientPolicy { /// <summary> /// 重试 /// </summary> public AsyncRetryPolicy<HttpResponseMessage> RetryPolicy { get; set; } /// <summary> /// 熔断 /// </summary> public AsyncCircuitBreakerPolicy BreakerPolicy { get; set; } /// <summary> /// 降级 /// </summary> public AsyncFallbackPolicy<UserInfo> FallbackPolicy { get; set; } /// <summary> /// 限流 /// </summary> public AsyncRateLimitPolicy RateLimitPolicy { get; set; } public ClientPolicy() { // 重试5次,等待时间是:2 4 8 16 32 RetryPolicy = Policy.HandleResult<HttpResponseMessage>(p=>!p.IsSuccessStatusCode) .WaitAndRetryAsync(5,retryAttempt=>TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // 连续发生了Exception异常之后,就会抛出熔断异常BrokenCircuitException // 一分钟出现2次异常则进行熔断,一分钟之后恢复请求 BreakerPolicy = Policy.Handle<Exception>().CircuitBreakerAsync(2, TimeSpan.FromMinutes(1)); // 如果捕捉到了Exception 异常之后,返回一个UserInfo 实例 FallbackPolicy = Policy<UserInfo>.Handle<Exception>() .FallbackAsync(new UserInfo()); // 1秒钟只允许访问两次 RateLimitPolicy = Policy.RateLimitAsync(2, TimeSpan.FromSeconds(1)); } }
Program.cs里面进行注册
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddSwaggerGen(); builder.Services.AddSingleton(new ClientPolicy()); builder.Services.AddHttpClient(); // 超时 builder.Services.AddHttpClient("timeoutTest").AddPolicyHandler( Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(8))); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
添加一个控制器RequestController
[ApiController] [Route("[controller]/[action]")] public class RequestController:ControllerBase { private readonly ClientPolicy _clientPolicy; private readonly IHttpClientFactory _httpClientFactory; public RequestController(ClientPolicy clientPolicy, IHttpClientFactory httpClientFactory) { _clientPolicy = clientPolicy; _httpClientFactory = httpClientFactory; } /// <summary> /// 重试 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public async Task<ActionResult> TestRetry(int id) { using var httpClient = _httpClientFactory.CreateClient(); var response = await _clientPolicy.RetryPolicy.ExecuteAsync(() => httpClient.GetAsync($"http://localhost:5258/Response/TestRetry/{id}")); if (response.IsSuccessStatusCode) { Console.WriteLine($"请求成功,Code={response.StatusCode}"); } else { Console.WriteLine($"请求失败,Code={response.StatusCode}"); } return Content($"请求完毕:{response.StatusCode}"); } /// <summary> /// 熔断 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public async Task<ActionResult> TestBreaker(int id) { using var httpClient = _httpClientFactory.CreateClient(); try { var response = await _clientPolicy.BreakerPolicy.ExecuteAsync(() => httpClient.GetStringAsync($"http://localhost:5258/Response/TestBreaker/{id}")); return Ok(response); } catch (BrokenCircuitException e) { Console.WriteLine("发生了熔断"); return Ok("熔断了"); } catch (Exception e) { Console.WriteLine("发生了异常"); return Ok("异常了"); } } /// <summary> /// 降级 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public async Task<ActionResult> TestFallback(int id) { using var httpClient = _httpClientFactory.CreateClient(); var response = await _clientPolicy.FallbackPolicy.ExecuteAsync(async () => { var json = await httpClient.GetStringAsync($"http://localhost:5258/Response/TestFallback/{id}"); return JsonConvert.DeserializeObject<UserInfo>(json); }); return Ok(response); } /// <summary> /// 超时处理器 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public async Task<ActionResult> TestTimeout(int id) { using var httpClient = _httpClientFactory.CreateClient("timeoutTest"); try { var response = await httpClient.GetStringAsync($"http://localhost:5258/Response/TestTimeout/{id}"); return Ok(response); } catch (TimeoutRejectedException e) { Console.WriteLine("发生了超时异常"); return Ok("超时了"); } catch (Exception e) { Console.WriteLine("发生了异常"); return Ok("异常了"); } } /// <summary> /// 限流 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public async Task<ActionResult> TestRateLimit(int id) { using var httpClient = _httpClientFactory.CreateClient(); try { var response = await _clientPolicy.RateLimitPolicy.ExecuteAsync(async () => { var json = await httpClient.GetStringAsync($"http://localhost:5258/Response/TestRateLimit/{id}"); return JsonConvert.DeserializeObject<UserInfo>(json); }); return Ok(response); } catch (RateLimitRejectedException e) { Console.WriteLine($"发生了限流异常:{e.Message}"); } catch (Exception e) { Console.WriteLine($"发生了异常:{e.Message}"); } return Ok("请求结束"); } }
Polly.Response项目添加ResponseController控制器
/// <summary> /// 响应 /// </summary> [ApiController] [Route("[controller]/[action]")] public class ResponseController:ControllerBase { /// <summary> /// 重试 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public ActionResult TestRetry(int id) { var randomNumber = new Random().Next(1,100); if (id>randomNumber) { Console.WriteLine("请求成功 200"); return Ok("请求成功"); } Console.WriteLine("请求失败 400"); return BadRequest("请求失败"); } /// <summary> /// 熔断 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public ActionResult TestBreaker(int id) { var randomNumber = new Random().Next(1,100); if (id>randomNumber) { Console.WriteLine("请求成功 200"); return Ok(new UserInfo(){Id = 1,NickName = "张三"}); } throw new Exception("发生了异常"); } /// <summary> /// 降级 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public ActionResult TestFallback(int id) { var randomNumber = new Random().Next(1,100); if (id>randomNumber) { Console.WriteLine("请求成功 200"); return Ok(new UserInfo{Id = 1,NickName = "张三"}); } throw new Exception("发生了异常"); } /// <summary> /// 超时 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public ActionResult TestTimeout(int id) { var randomNumber = new Random().Next(1,10); Thread.Sleep(randomNumber*1000); return Ok(new UserInfo {Id = id, NickName = "张三"}); } /// <summary> /// 限流 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}")] public ActionResult TestRateLimit(int id) { return Ok(new UserInfo {Id = id, NickName = "张三"}); } }