c# - Polly 断路器策略和 HttpClient 与 ASP.NET Core API
问题描述
我在设置 Polly 的 CircuitBreaker 时遇到了问题HttpClient
。
具体来说,CircuitBreaker
用于HttpClient
ASP.NET Core Web API 控制器,链接如下:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory
下面是我想要的
重试策略:如果出现瞬时错误,则每个请求重试 3 次。
cicuit 断路器策略:如果在所有请求中发生五个瞬时错误,则生效。
问题
尽管重试策略正常工作,但断路器策略不起作用。
在 _httpClient.SendAsync() 发生 5 次异常后,CarController 仍会收到请求,并且不会暂停 30 秒(控制器会立即处理请求)。
HandledEventsAllowedBeforeBreaking: 5
DurationOfBreakInSeconds: 30
我在这里错过了什么吗?
配置服务
配置 Polly 重试和断路器策略,并指定自定义 HttpClient,HttpClientService
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddHttpClient<IHttpClientService, HttpClientService>()
.AddPolicyHandler((service, request) =>
HttpPolicyExtensions.HandleTransientHttpError()
.WaitAndRetryAsync(3,
retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)),
onRetry: (outcome, timespan, retryCount, context) =>
{
service.GetService<ILog>().Error("Delaying for {delay}ms, then making retry {retry}.",
timespan.TotalMilliseconds, retryCount);
}
)
)
)
.AddPolicyHandler((service, request) =>
HttpPolicyExtensions.HandleTransientHttpError()
//Further external requests are blocked for 30 seconds if five failed attempts occur sequentially.
//Circuit breaker policies are stateful.All calls through this client share the same circuit state.
.CircuitBreakerAsync(5,
TimeSpan.FromSeconds(30),
(result, timeSpan, context)=>
service.GetService<ILog>().Error("CircuitBreaker onBreak for {delay}ms", timeSpan.TotalMilliseconds),
context =>
service.GetService<ILog>().Error("CircuitBreaker onReset")));
}
汽车控制器
IHttpClientService
在 Polly 政策中指定ConfigureServices
。HttpClientService
使用HttpClient
.
断路器不工作:即使从 发生 5 个瞬时错误(例如HttpRequestException
)_httpClient.SendAsync()
,CarController 仍然可以接收请求,并且不会暂停 30 秒。
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class CarController : ControllerBase
{
private readonly ILog _logger;
private readonly IHttpClientService _httpClientService;
private readonly IOptions<Config> _config;
public CarController(ILog logger, IHttpClientService httpClientService, IOptions<Config> config)
{
_logger = logger;
_httpClientService = httpClientService;
_config = config;
}
[HttpPost]
public async Task<ActionResult> Post()
{
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
string body = reader.ReadToEnd();
var statusCode = await _httpClientService.PostAsync(
"url",
new Dictionary<string, string>
{
{"headerID", "Id"}
},
body);
return StatusCode((int)statusCode);
}
}
}
HttpClientService
似乎 HttpClient 在请求之间没有状态。
断路器不工作:即使从 发生 5 个瞬时错误(例如HttpRequestException
)_httpClient.SendAsync()
,CarController 仍然可以接收请求,并且不会暂停 30 秒。
public class HttpClientService
{
private readonly HttpClient _httpClient;
public HttpClientService(HttpClient client)
{
_httpClient = client;
}
public async Task<HttpStatusCode> PostAsync(string url, Dictionary<string, string> headers, string body)
{
using (var content = new StringContent(body, Encoding.UTF8, "application/json"))
{
foreach (var keyValue in headers)
{
content.Headers.Add(keyValue.Key, keyValue.Value);
}
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = content
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return response.StatusCode;
}
}
ASP.NET 核心 API 2.2
更新 更新了 SetWaitAndRetryPolicy 扩展方法以使用 IServiceProvider。
解决方案
断路器策略是有状态的,用于跟踪调用之间的故障率,因此需要长期存在而不是根据请求创建。
贴出的代码中对 HttpClientFactory 的重载使用方式:
.AddPolicyHandler((service, request) => HttpPolicyExtensions.HandleTransientHttpError()
.CircuitBreakerAsync( /* etc */
正在根据请求制造断路器的实例,因此断路器永远没有时间建立故障状态。
该重载(和类似的)旨在根据请求的特征动态选择策略。但实际上发布的代码(编辑:最初发布的代码是)没有使用任何service, request
输入参数,因此您可以删除该(service, request) =>
部分,并使用 HttpClientFactory 上的重载,该重载采用策略实例:
.AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError()
.WaitAndRetryAsync(/* etc */))
.AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError()
.CircuitBreakerAsync(/* etc */))
HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */)
然后,由 HttpClientFactory 配置为使用它的 HttpClient 实例将使用由返回的单个、长期存在的断路器实例。
旁注(特别是对于任何想要在给定过载情况下使用断路器的读者):
可以在 HttpClientFactory 中将断路器与请求驱动的policySelector
重载一起使用。只需确保lambda 表达式选择单个实例,而不是每次请求都生成一个新实例。例如:
var circuitBreaker = HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */);
services.AddHttpClient<IHttpClientService, HttpClientService>()
.AddPolicyHandler((service, request) => circuitBreaker); // By way of example technique: more typically with this overload, there is some more complex logic to select different policies for different kinds of request.
编辑以回答评论中的问题:不必声明该实例static
以使其长期存在。Startup.ConfigureServices(...)
根据上面的代码示例,可以在使用前立即在方法中声明它。lambda 并在 HttpClientFactory 上对其进行配置将捕获它并使其长期存在。
该circuitBreaker
实例应该在您想要中断的调用之间共享。如果您将断路器附加到HttpClient
通过 HttpClientFactory 声明的特定配置,则通过该配置实例的所有调用HttpClient
稍后由 DI 从 HttpClientFactory 检索,将共享断路器,从而共同中断。
将断路器与 HttpClientFactory 一起使用时,这通常意味着您可以HttpClient
在 HttpClientFactory 上声明一个配置(类型化或命名),每个子系统都希望调用共同的断路器。
旁注:所选断路器的变体也基于连续故障计数触发。(提到以防万一一个额外的因素;发布的问题是指跨请求发生的 5 个错误,但不是特别连续。)
推荐阅读
- json - Sublime 中的 .json 文件上的这些红色箭头和黄色矩形是什么?
- javascript - Angular 8 表格行渲染指令问题
- r - 如何在一个图中绘制来自不同模型的估计值
- python - 每次用户输入后如何正确显示菜单?
- python - Python:查询器验证功能
- python - 用于下载股票基本数据的 TWS API 只运行第一个数据条目并忽略其他数据。谁来解决这个问题?
- gradle - Flutter:尽管几分钟前它运行良好,但无法启动项目
- c# - Xamarin 悬停事件
- javascript - Redux 状态已填充但不会在组件中呈现
- ruby-on-rails - 从视图调用控制器方法的问题