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 = "张三"});
    }
}

 

posted @ 2024-02-23 11:20  野码  阅读(481)  评论(0编辑  收藏  举报