.Net Core 微服务实战 - Polly:重试、熔断、限流

Polly:重试、熔断、限流

  • 源码及系列文章目录
  • Polly
    • Polly 组件包
    • Polly 的能力
  • 失败重试
    • 适合失败重试的场景
    • 实现失败重试注意点
  • 熔断
  • 限流
  • 缓存
  • 组合策略
    • 组合策略的优先级
  • 策略的状态

源码及系列文章目录

Git 源码 :https://github.com/tangsong1995/TS.Microservices
CSDN 资源 :https://download.csdn.net/download/qq_33649351/34675095

系列文章目录 :https://blog.csdn.net/qq_33649351/article/details/120998558

Polly

Polly 组件包

  • Polly
  • Polly.Extensions.Http
  • Microsoft.Extensions.Http.Polly

Polly 的能力

  • 失败重试:调用失败时,能自动重试。
  • 服务熔断:部分服务不可用时,快速响应熔断结果;避免持续请求不可用服务。
  • 超时处理:为请求设置超时时间,当超过超时时间时,按预定的操作进行处理
  • 舱壁隔离:限流,为服务定义最大流量和队列,避免服务因为压力过大而崩溃。
  • 缓存策略:以AOP的方式为服务嵌入缓存的机制。
  • 失败降级:当服务不可用时,响应一个友好的结果而不是报错。
  • 组合策略:将以上能力组合在一起。

失败重试

使用 AddTransientHttpErrorPolicy 添加瞬时 Http 请求失败策略:

public static IHttpClientBuilder AddPolly(this IHttpClientBuilder builder)
{
    builder.AddTransientHttpErrorPolicy(p => p.RetryAsync()); // p.RetryAsync() 表示重试一次,可指定重试次数
    return builder;
}

AddTransientHttpErrorPolicy 表示在以下情况会触发失败策略:

  • Network failures (as System.Net.Http.HttpRequestException)
  • HTTP 5XX status codes (server errors)
  • HTTP 408 status code (request timeout)

使用 WaitAndRetryAsync 指定重试时间间隔:

public static IHttpClientBuilder AddPolly(this IHttpClientBuilder builder)
{
    builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(1 * 2))); 
    return builder;
}

使用 WaitAndRetryForeverAsync ,直到请求成功,否则一直重试:

public static IHttpClientBuilder AddPolly(this IHttpClientBuilder builder)
{
    builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryForeverAsync(i => TimeSpan.FromSeconds(1 * 2))); 
    return builder;
}

自定义重试策略:

var reg = services.AddPolicyRegistry();
reg.Add("retryforever", Policy.HandleResult<HttpResponseMessage>(message =>
{
    return message.StatusCode == System.Net.HttpStatusCode.Created;
}).RetryForeverAsync());
services.AddHttpClient("httpclient").AddPolicyHandlerFromRegistry("retryforever");

通过注入委托实现动态定义重试策略:

var reg = services.AddPolicyRegistry();
reg.Add("retryforever", Policy.HandleResult<HttpResponseMessage>(message =>
{
    return message.StatusCode == System.Net.HttpStatusCode.Created;
}).RetryForeverAsync());
services.AddHttpClient("httpclient").AddPolicyHandlerFromRegistry((registry, message) =>
{
	// Get请求执行重试策略,其他请求不执行
    return message.Method == HttpMethod.Get ? registry.Get<IAsyncPolicy<HttpResponseMessage>>("retryforever") : Policy.NoOpAsync<HttpResponseMessage>();
});

适合失败重试的场景

  • 服务“失败”是短暂的,可自愈的
  • 服务是幂等的,重复调用不会有副作用

实现失败重试注意点

  • 设置失败重试次数
  • 设置带有步长策略的失败等待间隔
  • 设置降级响应
  • 设置断路器

熔断

添加熔断策略:

services.AddHttpClient("httpclient").AddPolicyHandler(Policy<HttpResponseMessage>.Handle<HttpRequestException>().CircuitBreakerAsync(
	     handledEventsAllowedBeforeBreaking: 10,
	     durationOfBreak: TimeSpan.FromSeconds(10),
	     onBreak: (r, t) => { },
	     onReset: () => { },
	     onHalfOpen: () => { }
	     ));

handledEventsAllowedBeforeBreaking:在报错十次后进行熔断
durationOfBreak:熔断时间
onBreak:发生熔断时触发的事件
onReset:熔断恢复时触发的事件
onHalfOpen:恢复之前验证服务是否可用(打一部分流量验证)

根据请求失败比例配置熔断策略:

services.AddHttpClient("httpclient").AddPolicyHandler(Policy<HttpResponseMessage>.Handle<HttpRequestException>().AdvancedCircuitBreakerAsync(
         failureThreshold: 0.8,
         samplingDuration: TimeSpan.FromSeconds(10),
         minimumThroughput: 100,
         durationOfBreak: TimeSpan.FromSeconds(20),
         onBreak: (r, t) => { },
         onReset: () => { },
         onHalfOpen: () => { }));

failureThreshold:请求失败比例,达到比例时进行熔断
samplingDuration:采样时间,在此规定的时间范围内请求有80%失败则进行熔断
minimumThroughput:最小吞吐量,采样时间内请求小于此数量时,不论失败比例是多少,都不触发熔断

当触发熔断时,会抛出熔断异常 BrokenCircuitException,我们可以为为熔断异常配置降级响应:

//熔断策略
var breakPolicy = Policy<HttpResponseMessage>.Handle<HttpRequestException>().AdvancedCircuitBreakerAsync(
    failureThreshold: 0.8,
    samplingDuration: TimeSpan.FromSeconds(10),
    minimumThroughput: 100,
    durationOfBreak: TimeSpan.FromSeconds(20),
    onBreak: (r, t) => { },
    onReset: () => { },
    onHalfOpen: () => { });

//降级策略
var message = new HttpResponseMessage()
{
    Content = new StringContent("{}")
};
var fallback = Policy<HttpResponseMessage>.Handle<BrokenCircuitException>().FallbackAsync(message);

var fallbackBreak = Policy.WrapAsync(fallback, breakPolicy);
services.AddHttpClient("httpclient").AddPolicyHandler(fallbackBreak);

上面代码的意义是:在十秒内,请求量超过100,并且失败比例超过80%时,触发熔断;并且会对 BrokenCircuitException 熔断异常进行降级,响应友好结果。

限流

定义限流策略:

//限流策略
var bulk = Policy.BulkheadAsync<HttpResponseMessage>(
    maxParallelization: 30,
    maxQueuingActions: 20,
    onBulkheadRejectedAsync: contxt => Task.CompletedTask
    );
services.AddHttpClient("httpclient").AddPolicyHandler(bulk);

maxParallelization:最大并发量
maxQueuingActions:最大队列数量
onBulkheadRejectedAsync:超出最大并发量和最大队列数量时的处理逻辑

当触发限流时,会抛出限流异常 BulkheadRejectedException,我们可以为为限流异常配置降级响应:

//限流策略
var bulk = Policy.BulkheadAsync<HttpResponseMessage>(
    maxParallelization: 30,
    maxQueuingActions: 20,
    onBulkheadRejectedAsync: contxt => Task.CompletedTask
    );

var fallback2 = Policy<HttpResponseMessage>.Handle<BulkheadRejectedException>().FallbackAsync(new HttpResponseMessage()
{
    Content = new StringContent("{}")
});
var fallbackbulk = Policy.WrapAsync(fallback2, bulk);
services.AddHttpClient("httpclient").AddPolicyHandler(fallbackbulk);

上面代码的意义是:当超出最大并发量30,且超出队列数量20时,触发限流;并且会对 BulkheadRejectedException 限流异常进行降级,响应友好结果。

缓存

public class MyAsyncCacheProvider : IAsyncCacheProvider
{
    public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext)
    {
        throw new NotImplementedException();
    }

    public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext)
    {
        throw new NotImplementedException();
    }
}
services.AddSingleton<IAsyncCacheProvider, MyAsyncCacheProvider>();
var serviceProvider = services.BuildServiceProvider();
var cachePolicy = Policy.CacheAsync<HttpResponseMessage>(serviceProvider.GetRequiredService<IAsyncCacheProvider>(), TimeSpan.FromSeconds(10));
services.AddHttpClient("httpclient").AddPolicyHandler(cachePolicy);

组合策略

定义熔断、降级、重试策略,然后组合起来:

//熔断策略
var breakPolicy = Policy<HttpResponseMessage>.Handle<HttpRequestException>().AdvancedCircuitBreakerAsync(
    failureThreshold: 0.8,
    samplingDuration: TimeSpan.FromSeconds(10),
    minimumThroughput: 100,
    durationOfBreak: TimeSpan.FromSeconds(20),
    onBreak: (r, t) => { },
    onReset: () => { },
    onHalfOpen: () => { });

//降级策略
var message = new HttpResponseMessage()
{
    Content = new StringContent("{}")
};
var fallback = Policy<HttpResponseMessage>.Handle<BrokenCircuitException>().FallbackAsync(message);

//重试策略
var retry = Policy<HttpResponseMessage>.Handle<Exception>().WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(1));

var fallbackBreak = Policy.WrapAsync(fallback, retry, breakPolicy);
services.AddHttpClient("httpclient").AddPolicyHandler(fallbackBreak);

组合策略的优先级

组合策略的顺序,与中间件类似,后注册的执行在最内层,也就是最先起作用。

策略的状态

熔断、限流及缓存策略都是有状态的。其他策略是无状态的。需要为不同服务定义各自的策略。
如有以下限流策略:

//限流策略
var bulk = Policy.BulkheadAsync<HttpResponseMessage>(
    maxParallelization: 30,
    maxQueuingActions: 20,
    onBulkheadRejectedAsync: contxt => Task.CompletedTask
    );
services.AddHttpClient("httpclient").AddPolicyHandler(bulk);
services.AddHttpClient("httpclient2").AddPolicyHandler(bulk);

那么 httpclient 和 httpclient2 两个服务的请求会共享 maxParallelization 和 maxQueuingActions 。

你可能感兴趣的:(.net,core,微服务实战,微服务,.net,core,Polly)