借助Aspire中新增的Microsoft.Extensions.ServiceDiscovery库,我们可以很容易的做到服务发现,那么服务节点的熔断限流以及重试等弹性机制.NET是怎么处理的呢?
比如下图的微服务场景:
这里就不得不提到 Microsoft.Extensions.Http.Polly
这个Polly的封装库了,这个库是从NETCore2.1增加的,我们可以很容易的对IHttpClientBuilder扩展弹性机制,比如下面的代码:
builder.Services.AddHttpClient("qrcode", client =>
{
client.BaseAddress = new("http://qrcode");
})
.AddTransientHttpErrorPolicy((builder) =>
{
// 重试3次,每次间隔5秒
var retryPolicy = builder.WAItAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(5d));
// 熔断器,失败3次后,30秒内不再请求
var circuitBreakerPolicy = builder.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));
// 重试策略包装熔断器
return retryPolicy.WrapAsync(circuitBreakerPolicy);
});
然鹅Microsoft.Extensions.Http.Polly
库是早些年就存在的产物了,针对Polly V8及后续的版本巨硬在 NET8后增加了 Microsoft.Extensions.Http.Resilience
库作为替代方案.
下面是巨硬给出的两个版本的性能差异:
Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|
StandardPipeline_Polly_V7 | 3.236 us | 0.0130 us | 0.0187 us | 1.00 | 0.1488 | 3816 B | 1.00 |
StandardPipeline_Polly_V8 | 3.104 us | 0.0237 us | 0.0317 us | 0.96 | 0.0381 | 1008 B | 0.26 |
速度较快的同时,基于 Polly v8 构建的Microsoft.Extensions.Http.Resilience使用的内存减少了4倍之多。
所以至NET8+以后我推荐使用Microsoft.Extensions.Http.Resilience
替代Microsoft.Extensions.Http.Polly
库
下面我们使用新库实现模拟的策略:
builder.Services.AddHttpClient("qrcode", client =>
{
client.BaseAddress = new("http://qrcode");
})
// Microsoft.Extensions.Http.Polly实现的代码:
.AddTransientHttpErrorPolicy((builder) =>
{
// 重试3次,每次间隔5秒
var retryPolicy = builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(5d));
// 熔断器,失败3次后,30秒内不再请求
var circuitBreakerPolicy = builder.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));
// 重试策略包装熔断器
return retryPolicy.WrapAsync(circuitBreakerPolicy);
})
// Microsoft.Extensions.Http.Resilience实现代码:
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);// 总的超时时间
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(5);//每次重试的超时时间
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(30);//熔断时间
})
;
当然大多数情况弹性机制是放到配置文件或者其他持久层的这个时候我们可以使用Configure(IConfigurationSection)
或者 Configure(Action<HttpStandardResilienceOptions, IServiceProvider>)
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(5);// 超时时间
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(5);//每次重试的超时时间
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(30);//熔断时间
})
.Configure((options, sp) =>
{
// 配置来自自定义的Provider
var myResiliencePolicy = sp.GetRequiredService<IResiliencePolicyProvider>().GetResiliencePolicy("MyPolicy");
options.Retry.MaxRetryAttempts = myResiliencePolicy.Retry.MaxRetryAttempts;
});
从代码上来说也优雅了一些
当然也可以使用AddResilienceHandler
扩展方法实现一些比较个性化的配置需求,这个就比较类似于Microsoft.Extensions.Http.Polly:
builder.Services.AddHttpClient("qrcode", client =>
{
client.BaseAddress = new("http://qrcode");
})
.AddResilienceHandler("MyPolicy", builder =>
{
builder.AddTimeout(TimeSpan.FromSeconds(5));
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(10),
});
//其他的机制....
});
参考文档 https://github.com/dotnet/docs/blob/main/docs/core/resilience/http-resilience.md
https://devblogs.microsoft.com/dotnet/building-resilient-cloud-services-with-dotnet-8/