gpt4 book ai didi

c# - 使用 Polly.Net 的嵌套重试和断路器策略的意外行为

转载 作者:行者123 更新时间:2023-12-02 01:56:40 25 4
gpt4 key购买 nike

我编写了一个基于重试的弹性策略和一个断路器策略。现在工作正常,但其行为存在问题。

我注意到当断路器处于半开时,onBreak()事件再次被执行以关闭电路,触发了一次额外的重试对于重试策略(这是健康验证之外的半开状态)。

让我一步一步解释:

我已经为重试和断路器定义了两个强类型策略:

static Policy<HttpResponseMessage> customRetryPolicy;
static Policy<HttpResponseMessage> customCircuitBreakerPolicy;

static HttpStatusCode[] httpStatusesToProcess = new HttpStatusCode[]
{
HttpStatusCode.ServiceUnavailable, //503
HttpStatusCode.InternalServerError, //500
};

重试策略以这种方式工作:每个请求重试两 (2) 次,每次重试之间等待五 (5) 秒。如果内部断路器断开,不得重试。仅重试 500 和 503 Http 状态。

customRetryPolicy = Policy<HttpResponseMessage>   

//Not execute a retry if the circuit is open
.Handle<BrokenCircuitException>( x =>
{
return !(x is BrokenCircuitException);
})

//Stop if some inner exception match with BrokenCircuitException
.OrInner<AggregateException>(x =>
{
return !(x.InnerException is BrokenCircuitException);
})

//Retry if status are:
.OrResult(x => { return httpStatusesToProcess.Contains(x.StatusCode); })

// Retry request two times, wait 5 seconds between each retry
.WaitAndRetry( 2, retryAttempt => TimeSpan.FromSeconds(5),
(exception, timeSpan, retryCount, context) =>
{
System.Console.WriteLine("Retrying... " + retryCount);
}
);

断路器策略以这种方式工作:允许连续最多三 (3) 次故障,然后打开电路三十 (30) 秒。开路仅适用于 HTTP-500。

customCircuitBreakerPolicy = Policy<HttpResponseMessage>

// handling result or exception to execute onBreak delegate
.Handle<AggregateException>(x =>
{ return x.InnerException is HttpRequestException; })

// just break when server error will be InternalServerError
.OrResult(x => { return (int) x.StatusCode == 500; })

// Broken when fail 3 times in a row,
// and hold circuit open for 30 seconds
.CircuitBreaker(3, TimeSpan.FromSeconds(30),
onBreak: (lastResponse, breakDelay) =>{
System.Console.WriteLine("\n Circuit broken!");
},
onReset: () => {
System.Console.WriteLine("\n Circuit Reset!");
},
onHalfOpen: () => {
System.Console.WriteLine("\n Circuit is Half-Open");
});

最后,这两个策略以这种方式嵌套:

try
{
customRetryPolicy.Execute(() =>
customCircuitBreakerPolicy.Execute(() => {

//for testing purposes "api/values", is returning 500 all time
HttpResponseMessage msResponse
= GetHttpResponseAsync("api/values").Result;

// This just print messages on console, no pay attention
PrintHttpResponseAsync(msResponse);

return msResponse;

}));
}
catch (BrokenCircuitException e)
{
System.Console.WriteLine("CB Error: " + e.Message);
}

我期望的结果是什么?

  1. 第一个服务器响应是 HTTP-500(如预期的那样)
  2. 重试 #1,失败(如预期)
  3. 重试 #2,失败(如预期)
  4. 由于我们有三个故障,断路器现在打开(如预期的那样)
  5. 太棒了!这非常有效!
  6. 断路器在接下来的三十 (30) 秒内打开(如预期)
  7. 30 秒后,断路器半开(如预期)
  8. 一次尝试检查端点健康状况(如预期)
  9. 服务器响应是 HTTP-500(符合预期)
  10. 断路器在接下来的三十 (30) 秒内打开(如预期)
  11. 问题在这里:当断路器已经打开时,会启动额外的重试!

看图片:

enter image description here

enter image description here

enter image description here

我正在尝试理解这种行为。为什么当断路器打开第二次、第三次、...、N 次时,还要执行一次额外的重试?

我已经查看了重试的机器状态模型和断路器策略,但我不明白为什么要执行此额外的重试。

断路器的流程: https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#putting-it-all-together-

重试策略的流程: https://github.com/App-vNext/Polly/wiki/Retry#how-polly-retry-works

这真的很重要,因为等待重试的时间(本例为 5 秒),最后,这对于高并发来说是浪费时间。

任何帮助/指导,将不胜感激。非常感谢。

最佳答案

Polly.Context您可以在两个策略之间交换信息(在您的情况下:重试和断路器)。上下文基本上是一个 Dictionary<string, object> .

所以,诀窍是在 onBreak 上设置一个键然后在 sleepDurationProdiver 中使用该值.

让我们从内部断路器策略开始:

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return Policy<HttpResponseMessage>
.HandleResult(res => res.StatusCode == HttpStatusCode.InternalServerError)
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2),
onBreak: (dr, ts, ctx) => { ctx[SleepDurationKey] = ts; },
onReset: (ctx) => { ctx[SleepDurationKey] = null; });
}
  • 它在 3 个后续失败请求后中断
  • 它停留在Open状态 2 秒,然后过渡到 HalfOpen
  • 它使用 durationOfBreak 在上下文中设置一个键值(value)
  • 当 CB 回到“正常”时 Closed state ( onReset ) 它删除这个值

现在,让我们继续重试策略:

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return Policy<HttpResponseMessage>
.HandleResult(res => res.StatusCode == HttpStatusCode.InternalServerError)
.Or<BrokenCircuitException>()
.WaitAndRetryAsync(4,
sleepDurationProvider: (c, ctx) =>
{
if (ctx.ContainsKey(SleepDurationKey))
return (TimeSpan)ctx[SleepDurationKey];
return TimeSpan.FromMilliseconds(200);
},
onRetry: (dr, ts, ctx) =>
{
Console.WriteLine($"Context: {(ctx.ContainsKey(SleepDurationKey) ? "Open" : "Closed")}");
Console.WriteLine($"Waits: {ts.TotalMilliseconds}");
});
}
  • 当 StatusCode 为 500 时触发
    • 或者当出现 BrokenCircuitException
  • 它最多触发 4 次(因此,总共 5 次尝试)
  • 它根据上下文设置 sleep 持续时间
    • 如果 key 不在上下文中(CB 处于 Open 状态),则它会在 200 毫秒后返回
    • 如果 key 存在于上下文中(CB 不处于 Open 状态),则它返回上下文中的值
      • 注意:您可以为此值增加几百毫秒以避免竞争条件
  • 它在 onRetry 中向控制台打印一些值仅用于调试目的

最后让我们连接策略并测试它

const string SleepDurationKey = "Broken"; 
static HttpClient client = new HttpClient();
static async Task Main()
{
var strategy = Policy.WrapAsync(GetRetryPolicy(), GetCircuitBreakerPolicy());
await strategy.ExecuteAsync(async () => await Get());
}

static Task<HttpResponseMessage> Get()
{
return client.GetAsync("https://httpstat.us/500");
}
  • 它使用 http://httpstat.us模拟重载下游的网站
  • 它结合/链接了两个策略(CB inner,Retry outer)
  • 它调用Get异步方法

handledEventsAllowedBeforeBreaking是2

输出

Context: Closed
Waits: 200
Context: Open
Waits: 2000
Context: Open
Waits: 2000
Context: Open
Waits: 2000

handledEventsAllowedBeforeBreaking是3

输出

Context: Closed
Waits: 200
Context: Closed
Waits: 200
Context: Open
Waits: 2000
Context: Open
Waits: 2000

handledEventsAllowedBeforeBreaking是4

输出

Context: Closed
Waits: 200
Context: Closed
Waits: 200
Context: Closed
Waits: 200
Context: Open
Waits: 2000

关于c# - 使用 Polly.Net 的嵌套重试和断路器策略的意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69534878/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com