当前位置: 首页 > 知识库问答 >
问题:

Polly CircuitBreaker在断路时更改HttpClient基地址以继续执行请求

百里景山
2023-03-14

目前,我有一个配置了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,并在将地址从主地址更改为故障转移的情况下重试一次以继续。

  1. 对主地址-500代码的请求
  2. 对主地址-500代码的请求
  3. 对主地址-500代码的请求(连续3次失败)
  4. 电路中断60秒
  5. 对主地址的请求-BrokenCircuitException被重试策略捕获,调用故障转移
  6. 对主地址的请求-BrokenCircuitException被重试策略捕获,调用故障转移
  7. 对主地址的请求-BrokenCircuitException被重试策略捕获,调用故障转移
  8. 对主地址的请求-BrokenCircuitException被重试策略捕获,调用故障转移
  9. (60秒后)电路半开路-(这里可以再断开60秒或开路-假设开路)
  10. 对主地址-200代码的请求

我想

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是如何工作的以及有什么作用
  • 策略可以包装,并且有包装的推荐顺序
  • Microsoft的断路器示例
  • 断路器的其他示例
  • 回退策略

我尝试了几种不同的方案,将断路器策略与重试策略相结合,以在启动文件中的客户端杠杆上实现所需的目标。最后一个状态是下面的。策略是按照重试能够捕获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");
        });
}

我省略了onbreakonresetonhalfopen方法,因为它们只是打印一些消息。

更新:从控制台添加日志。

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 Retries on Breaked circuit异常引发:System.Private.corelib.dll中的”System.AggregateException“

更新2:使用配置了策略的客户端向类添加了引用URL

更新3:该项目已经更新,以便WeatherService2.get的实现以所需的方式工作:当主服务不可用时,电路断开,使用falover服务直到电路关闭。这将是这个问题的答案,但是我想探索一个解决方案,在这个解决方案中,使用WeatherService.get,并在Startup上设置适当的策略和客户端,可以获得相同的结果。

使用客户端对类的引用。使用类对项目的引用。

在上面的日志中可以看到异常抛出:system.private.corelib.dll中的“System.AggregateException”,这是由断路器抛出的-这不是预期的,因为有重试包装断路器。

共有1个答案

王德华
2023-03-14

我已经下载了你的项目并玩了它,所以以下是我的观察:

  • 因为您的代码使用阻塞异步调用(.result),所以您会看到AggregateException
public IEnumerable<WeatherForecast> Get()
{
    HttpResponseMessage response = null;
    try
    {
        response = _client.GetAsync(string.Empty).Result; //AggregateException  
    }
    catch (Exception e)
    {
        Debug.WriteLine($"{e.Message}");
    }
    ...
}
  • 为了解开AggregateExceptionInnerExceptionInnerException,需要使用Await
public async Task<IEnumerable<WeatherForecast>> Get()
{
    HttpResponseMessage response = null;
    try
    {
        response = await _client.GetAsync(string.Empty); //BrokenCircuitException
    }
    catch (Exception e)
    {
        Debug.WriteLine($"{e.Message}");
    }
    ...
}

每当您将一个策略包装成另一个策略时,就可能发生升级。这意味着如果内部不能处理问题,那么它将把同样的问题传播到外部,外部可能不能处理它。如果最外层没有处理问题,那么(大多数情况下)原始异常将被抛给弹性策略的使用者(它是策略的组合)。

在这里您可以找到更多关于升级的详细信息。

让我们在您的案例中回顾一下这个概念:

var policyWrapper = Policy.WrapAsync(retryPolicy, circuitBreaker);

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(3, TimeSpan.FromSeconds(45), ...);
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return Policy<HttpResponseMessage>
        .Handle<Exception>()
        .RetryAsync(1, ...);
}
    null
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(3, TimeSpan.FromSeconds(45), ...);
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<Exception>()
        .RetryAsync(1, ...);
}
  1. 初始请求(1.尝试)是针对https://httpstat.us/500
  2. 发出的
  3. 返回500,这将使连续的瞬态故障从0增加到1
  4. CB将问题升级到重试
  5. 重试正在处理状态500,因此重试立即发出另一次尝试
  6. https://httpstat.us/500
  7. 发出第一个重试请求(2.尝试)
  8. 返回500,这将使连续的瞬态故障从1增加到2
  9. CB将问题升级到重试
  10. 即使重试正在处理状态500,它也不会触发,因为它达到了它的retrycount(1)
  11. HttpClient返回httpresponsemessageinternalservererrorstatuscode.

现在,让我们将连续失败计数从3降低到1,并显式处理BrokenCircuitException:

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(1, TimeSpan.FromSeconds(45), ...);
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<BrokenCircuitException>()
        .RetryAsync(1, ...);
}
  1. 初始请求(1.尝试)是针对https://httpstat.us/500
  2. 发出的
  3. 返回500,这将使连续的瞬态故障从0增加到1
  4. 断路器因达到预定义阈值而断开
  5. CB将问题升级到重试
  6. 重试正在处理状态500,因此重试立即发出另一次尝试
  7. https://httpstat.us/500
  8. 发出第一个重试请求(2.尝试)
  9. CB阻止此调用,因为它已中断
  10. CB抛出BrokenCircuitException
  11. 即使重试处理BrokenCircuitException,它也不会触发,因为它达到了它的retrycount(1)
  12. 重试抛出原始异常(BrokenCircuitException),因此HttpClient的GetAsync将抛出该异常。

最后,让我们将重试次数从1增加到2:

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(1, TimeSpan.FromSeconds(45), ...);
}

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<BrokenCircuitException>()
        .RetryAsync(2, ...);
}
  1. 初始请求(1.尝试)是针对https://httpstat.us/500
  2. 发出的
  3. 返回500,这将使连续的瞬态故障从0增加到1
  4. 断路器因达到预定义阈值而断开
  5. CB将问题升级到重试
  6. 重试正在处理状态500,因此重试立即发出另一次尝试
  7. https://httpstat.us/500
  8. 发出第一个重试请求(2.尝试)
  9. CB阻止此调用,因为它已中断
  10. CB抛出BrokenCircuitException
  11. Retry正在处理BrokenCircuitException,它没有超过它的retryCount,所以它立即发出另一次尝试
  12. 针对https://httpstat.us/500
  13. 发出第二次重试请求(3.尝试)
  14. CB阻止此调用,因为它已中断
  15. CB抛出BrokenCircuitException
  16. 即使重试正在处理BrokenCircuitException,它也不会触发,因为它达到了它的retrycount(2)
  17. 重试将抛出原始异常(BrokenCircuitException),因此HttpClient的GetAsync将抛出该异常。

我希望这个练习能帮助您更好地理解如何创建一个弹性策略,在这个策略中,您可以通过升级问题来组合多个策略。

 类似资料:
  • 问题内容: 我正在通过Java与Eclipse和TestNG框架一起使用Selenium RC。我有以下代码片段: 第一个断言失败,执行被停止。但我想继续进一步的代码片段。 问题答案: Selenium IDE使用验证执行软断言,这意味着即使检查失败,测试仍将继续,并且可以在测试结束时或在发生硬断言时报告失败。 使用TestNG,可以通过使用自定义测试侦听器来拥有这些软断言。我已经在博客上记录了如

  • 在一些请求中,我们会做一些日志的推送、用户数据的统计等和返回给终端数据无关的操作。而这些操作,即使你用异步非阻塞的方式,在终端看来,也是会影响速度的。这个和我们的原则:终端请求,需要用最快的速度返回给终端,是冲突的。 这时候,最理想的是,获取完给终端返回的数据后,就断开连接,后面的日志和统计等动作,在断开连接后,后台继续完成即可。 怎么做到呢?我们先看其中的一种方法: local response

  • 我制作了一个程序,要求用户输入5位数字,然后程序将查找这些数字的总和。我想知道我怎样才能使程序在计算一次之后一遍又一遍地要求一个数字。我希望用户再试一次,直到他自己想退出。

  • 问题内容: 编辑:切换到一个更好的示例,并阐明了为什么这是一个真正的问题。 我想用Python编写在断言失败时继续执行的单元测试,这样我就可以在一个测试中看到多个失败。例如: 在这里,测试的目的是确保Car’s正确设置其字段。我可以将其分解为四个方法(这通常是个好主意),但是在这种情况下,我认为将其保留为测试单个概念的单个方法(“对象已正确初始化”)更容易理解。 如果我们认为最好不要破坏该方法,那

  • 问题内容: 我在Mac从属计算机上有一个iOS存档作业,这有时会花费很长时间,有时需要30分钟。问题是ssh长时间连接经常断开连接并导致任务失败。 现在我想问一下如何避免这个问题?我要寻找的是长时间连接断开但任务继续执行时。我能怎么做? 问题答案: 自2014年以来,添加keepAlive选项是一项功能请求 作为建议的解决方法,此故障单包括: 1. 更改通过添加下面一行到这个文件的末尾。这告诉ss

  • 问题内容: 我知道上面的脚本不起作用。因此,如果需要将带有break的函数或继续放入循环,该如何写? 问题答案: 一个函数不能导致中断或继续调用它的代码。中断/继续实际上必须出现在循环内。您的选择是: 从funcA返回一个值并使用它来决定是否中断 在funcA中引发异常并将其捕获在调用代码中(或调用链中更高的位置) 写一个生成器来封装中断逻辑,然后在生成器上进行迭代 通过#3我的意思是这样的: 这