【发布时间】:2021-03-20 13:48:53
【问题描述】:
我现在有什么?
目前,我有一个配置了RetryAsync 策略的客户端,该策略使用主地址并在发生故障时切换到故障转移地址。连接详细信息从机密管理器中读取。
services
.AddHttpClient ("MyClient", client => client.BaseAddress = PlaceholderUri)
.ConfigureHttpMessageHandlerBuilder (builder => {
// loads settings from secret manager
var settings = configLoader.LoadSettings().Result;
builder.PrimaryHandler = new HttpClientHandler {
Credentials = new NetworkCredential (settings.Username, settings.Password),
AutomaticDecompression = DecompressionMethods.GZip
};
var primaryBaseAddress = new Uri (settings.Host);
var failoverBaseAddress = new Uri (settings.DrHost);
builder.AdditionalHandlers.Add (new PolicyHttpMessageHandler (requestMessage => {
var relativeAddress = PlaceholderUri.MakeRelativeUri (requestMessage.RequestUri);
requestMessage.RequestUri = new Uri (primaryBaseAddress, relativeAddress);
return HttpPolicyExtensions.HandleTransientHttpError ()
.RetryAsync ((result, retryCount) =>
requestMessage.RequestUri = new Uri (failoverBaseAddress, relativeAddress));
}));
});
我想达到什么目标?
一般
我的客户可以使用主服务或故障转移服务。当主服务器关闭时,使用故障转移直到主服务器备份。当两者都关闭时,我们会收到警报,并且可以通过秘密管理器动态更改服务地址。
在代码中
现在我还想介绍一个CircuitBreakerPolicy 并将这两个策略链接在一起。我正在寻找一种封装的配置,并且在客户端级别而不是在使用该客户端的类上处理错误。
场景解释
让我们假设有一个断路器策略包装在具有单个客户端的重试策略中。
断路器配置为在 暂时错误 3 次尝试失败 后将电路断开 60 秒在主基地址上。 OnBreak - 地址从主地址更改为故障转移。
重试策略配置为处理BrokenCircuitException,并重试一次,地址从主地址更改为故障转移以继续。
- 请求主地址 - 500 代码
- 请求主地址 - 500 代码
- 主地址请求 - 500 代码(连续 3 次失败)
- 电路中断 60 秒
- 对主地址的请求 -
BrokenCircuitException被重试策略捕获,调用故障转移 - 对主地址的请求 -
BrokenCircuitException被重试策略捕获,调用故障转移 - 对主地址的请求 -
BrokenCircuitException被重试策略捕获,调用故障转移 - 对主地址的请求 -
BrokenCircuitException被重试策略捕获,调用故障转移 - (60 秒后)电路半开 - (此处可以再断开 60 秒或打开 - 假设打开)
- 请求主地址 - 200 代码
正如articles 中所述,有一个解决方案是使用包装在回退中的断路器,但正如您所见,默认和回退的逻辑是在类中实现的,而不是在客户端级别。
我愿意
public class OpenExchangeRatesClient
{
private readonly HttpClient _client;
private readonly Policy _policy;
public OpenExchangeRatesClient(string apiUrl)
{
_client = new HttpClient
{
BaseAddress = new Uri(apiUrl),
};
var circuitBreaker = Policy
.Handle<Exception>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromMinutes(1)
);
_policy = Policy
.Handle<Exception>()
.FallbackAsync(() => GetFallbackRates())
.Wrap(circuitBreaker);
}
public Task<ExchangeRates> GetLatestRates()
{
return _policy
.ExecuteAsync(() => CallRatesApi());
}
public Task<ExchangeRates> CallRatesApi()
{
//call the API, parse the results
}
public Task<ExchangeRates> GetFallbackRates()
{
// load the rates from the embedded file and parse them
}
}
改写为
public class OpenExchangeRatesClient
{
private readonly HttpClient _client;
public OpenExchangeRatesClient (IHttpClientFactory clientFactory) {
_client = clientFactory.CreateClient ("MyClient");
}
public Task<ExchangeRates> GetLatestRates () {
return _client.GetAsync ("/rates-gbp-usd");
}
}
我读了些什么?
- CircutBreakerWorks 的用途和用途
- 可以包装策略,并且有一个 recommender order 用于包装
- Microsoft's example of Circuit breaker
- Other example of Circuit breaker
- Fallback Policy
我尝试了什么?
我尝试了几种不同的方案,将断路器策略与重试策略链接并结合起来,以在启动文件中的客户端杠杆上实现预期目标。最后一个状态如下。这些策略按照重试能够捕获BrokenCircuitException 的顺序进行包装,但情况并非如此。在消费者类上抛出异常,这不是预期的结果。虽然触发了RetryPolicy,但是还是抛出了消费者类的异常。
var retryPolicy = GetRetryPolicy();
var circuitBreaker = GetCircuitBreakerPolicy();
var policyWraper = Policy.WrapAsync(retryPolicy, circuitBreaker);
services
.AddHttpClient("TestClient", client => client.BaseAddress = GetPrimaryUri())
.AddPolicyHandler(policyWraper);
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
3,
TimeSpan.FromSeconds(45),
OnBreak,
OnReset,
OnHalfOpen);
}
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return Policy<HttpResponseMessage>
.Handle<Exception>()
.RetryAsync(1, (response, retryCount) =>
{
Debug.WriteLine("Retries on broken circuit");
});
}
我省略了 OnBreak、OnReset 和 OnHalfOpen 方法,因为它们只是打印一些消息。
更新:从控制台添加了日志。
Circuit broken (after 3 attempts)
Retries on broken
Exception thrown: 'System.AggregateException' in System.Private.CoreLib.dll
Retries on broken circuit
Exception thrown: 'System.AggregateException' in System.Private.CoreLib.dll
'CircuitBreakerPolicy.exe' (CoreCLR: clrhost): 加载'C:\Program 断路重试 抛出异常:System.Private.CoreLib.dll 中的“System.AggregateException”
更新 2:向使用配置了策略的客户端的类添加了参考 URL
更新 3:project 已更新,以便WeatherService2.Get 的实现以所需的方式工作:当主要服务不可用时,电路断开,使用故障转移服务直到电路关闭。这将是这个问题的答案,但是我想探索一个解决方案,其中使用 WeatherService.Get 并在 Startup 上使用适当的策略和客户端设置来实现相同的结果。
在上面的日志中可以看到Exception thrown: 'System.AggregateException' in System.Private.CoreLib.dll是由断路器抛出的——这是不期望的,因为有重试包裹断路器。
【问题讨论】:
-
您的
GetRetryPolicy将 retryCount 设置为 1。这意味着将有 2 次尝试(初始和重试)。因此,您的断路器不会中断,因为没有连续 3 次失败的尝试。 -
CircuitBreaker 在 3 次尝试后断开,然后由于抛出 BrokenCircutException 而触发重试。问题是消费类也抛出了异常,我希望它由包装断路器策略的重试策略处理。修改帖子以添加日志。
-
你能和我们分享一下消费方面吗?你也可以和我们分享一下
AggregateException里面是什么(InnerException是什么)? -
已将项目上传到公共仓库,并使用客户端添加了对类的引用以及对整个项目的引用。我还通过启用中断所有异常进行调试,
AggregateException具有内部异常Polly.CircuitBreaker.BrokenCircuitException<System.Net.Http.HttpResponseMessage>。 -
我还没有机会看你的第二个帖子,会尽快做和回答。
标签: c# asp.net-core polly circuit-breaker resiliency