翻译自:https://github.com/App-vNext/Polly/wiki/Circuit-Breaker
断路器
本页面介绍了原始Polly CircuitBreaker的操作和断路器的基本概念。高级断路器从Polly v4.2及更高版本可用,参考这里
为什么要使用断路器?请参看之前文章的讨论瞬时故障处理
基本语法
CircuitBreakerPolicy breaker = Policy .Handle<HttpRequestException>() .CircuitBreaker( exceptionsAllowedBeforeBreaking: 2, durationOfBreak: TimeSpan.FromMinutes(1) );
上面的例子将创建一个断路器,在通过策略执行的动作连续抛出两个HttpRequestException
异常后,该断路器将断开。电路将中断1分钟。
给出的语法示例是同步操作;异步操作也有类似的重载——参见readme和wiki。
Polly断路器是怎么工作的
断路器作为状态机
可以认为断路器是一个状态机,它有三个主要状态。
Closed
电路开始时是关闭的。当电路闭合时:
-
断路器执行置于其中的动作,判断这些动作是执行成功还是失败。
-
如果失败次数达到一定阈值,断路器打开。
-
最初的Polly CircuitBreaker会在通过策略执行的动作抛出N次任何已处理的异常后中断,其中N为策略配置的
int exceptionsAllowedBeforeBreaking
。 -
高级断路器在故障达到一定比例后断开:参见高级断路器。
-
对于导致电路跳闸的动作,原始异常被重新抛出,但电路状态转换为:Open
Open
当电路处于Open状态时:
-
任何通过策略放置以执行的操作将不会被执行。
-
相反,调用将快速失败,并出现
BrokenCircuitException
。
-
这个
BrokenCircuitException
的InnerException
中包含最后一个异常(导致电路中断的异常)。
-
电路在配置的
durationOfBreak
时间内保持打开状态。在经过durationOfBreak
之后,当下一个动作通过电路放置或如果查询了CircuitState
,电路转换为:Half-Open
Half-Open
当电路处于半开状态时:
-
下一次的调用操作将被视为试验,以确定电路的运行状况:传递给
.execute(…)
操作委托将会尝试执行。
-
(在断路期间
durationOfBreak
将只允许一次额外尝试。在半开状态下的其他所有尝试都被拒绝,并抛出BrokenCircuitException
。)
-
在这个试验期间:
-
如果接收到一个已处理的异常,该异常将被重新抛出,并且电路立即转换回open,并在配置的时间范围内再次保持打开状态。
-
如果收到一个成功的结果,电路转换回闭合。
-
如果接收到一个未处理的异常,电路仍然处于half-open状态。
Open/closed 语义
注意断路器的开/关的语义与门的语义相反。当考虑到软件断路器类似于电气开关时,最容易记住这一点:
-
关闭的 断路器 允许流动.
-
打开的 断路器 阻止流动.
异常处理
断路器可以认为是一个 “计量断路的设备”:他会计量通过他执行的操作所抛出的异常,并在超过配置的故障阈值时断路。
-
断路器不编排重试。
-
断路器不会像重试一样接收异常。所有通过断路器的委托操作所抛出的异常都会在内部再次抛出(不管断路器有没有处理这个异常)。断路器策略处理的异常会更新电路状态值(比如增加了错误次数,增大了错误百分比),而未经策略处理的则对状态值没有影响。
如果想要追求更加强大的功能,可以考虑使用PolicyWrap将重试与断路器结合使用。
CircuitBreaker
实例的作用域
CircuitBreakerPolicy
的实例自己维护内部状态,通过策略的多次调用跟踪故障:你必须在每次调用一个功能点执行时重用相同的 CircuitBreakerPolicy
实例,而不是在每次遍历代码时创建一个新的实例。
此外,您可以在多个功能点上共享相同的CircuitBreakerPolicy
实例,以使它们共同中断。
线程安全与锁
如上描述的那样,CircuitBreakerPolicy
实例在调用之间维护内部状态,以跟踪故障。,它使用了锁来保证线程安全。锁保持的时间尽可能短:只有在断路器读取或重新计算状态时才会加锁,在动作委托执行时不会加锁。
策略的内部操作是线程安全的,但这并不能神奇地使您通过策略执行的委托是线程安全的:如果您通过策略执行的委托不是线程安全的,那么它们仍然不是线程安全的。
Circuit state (for health reporting) (Polly v4.0+)
CircuitState state = breaker.CircuitState; /* CircuitState.Closed CircuitState.Open CircuitState.HalfOpen CircuitState.Isolated */
Closed: 电路工作正常,接受调用.
Open: 自动断路器将电路断开(如超过设定的阈值)。
HalfOpen: 在自动中断时间到期后 执行请求的第一个操作之前保持的状态。
Isolated: 电路已手动断路(见下文)
减少电路中断时抛出的异常
下面这样的代码模式可以用来减少电路打开时抛出的BrokenCircuitException
数量,如果这些异常影响到你的系统的性能指标:
if (breaker.State != breakerState.Open && breaker.State != breakerState.Isolated) { breaker.Execute(...) // place call }
注意,这样的代码不是必要的;它是高性能场景的一种选择。一般来说,调用breaker.Execute(...)
就足够了,并且断路器将自己决定该动作是否可以执行。此外,上述代码不能保证断路器不会阻止调用。在高度并发的环境中,断路器状态可能在执行if条件和执行操作之间发生变化。同样,在半开状态下,每个中断持续时间只允许一次执行。
手动控制(Polly v4.0+)
breaker.Isolate();
将电路置于手动打开状态。例如,这可以用于隔离已知存在故障的下游系统,或使其离线进行维护。
在这种状态下,通过策略执行的任何动作都将被阻塞(未执行);相反,调用将快速失败,并出现IsolatedCircuitException
。这个IsolatedCircuitException
扩展了BrokenCircuitException
,但不包含任何' InnerException '。
在调用如下代码之前,电路一直处于隔离(Isolated)状态:
breaker.Reset();
电路状态转换时的委托 (Polly v4.0+)
断路器可以配置电路状态转换时的委托(例如用于日志记录或其他目的)。
onBreak
: 该委托在电路自动转换为打开后,立即执行。传递的参数包括导致中断的异常、中断持续时间、(相关情况的)上下文context。
如果调用了 .Isolate()
,委托也会被执行。在本例中,断路的持续时间为TimeSpan.MaxValue
;传递的“Exception”值不确定。
onHalfOpen
: :该委托在电路转换为半开状态后,立即执行。注意:委托在自动中断时间范围过期后不会自动执行。它在下一次查询状态时执行—例如,在下一次尝试执行某个操作时,或下一次手动查询状态时。
onReset
: 该委托在电路转换为关闭状态后,立即执行,也就是在半开状态成功执行一次调用后执行。
如果手动调用.Reset()
,也会执行委托。
注意:在状态转换期间,所有状态转换委托都在由断路器持有的锁内执行。否则,在多线程环境中,委托所表示的状态更改可能无法保持(在执行委托时,它可能被其他事件取代)。因此,建议在状态转换委托中要避免长时间运行/或可能阻塞的操作。如果您在状态转换委托中执行阻塞操作,请注意任何阻塞都会通过策略阻塞其他操作。
注意: onBreak
, onReset
onHalfOpen
的委托都应该是同步的委托。然而,c#编译器会让你在没有警告的情况下将异步lambdas表达式赋值给Action
。正如Stephen Cleary在MSDN文章中所描述的那样,这可能会产生意想不到的运行时后果。调用代码不会等待异步void lambda完成,然后再继续运行。
将上面的在合起来讲一下
通过断路器调用的详细流程如下:
进一步阅读
更多关于断路器的文章可以在主要自述文件中的断路器部分下面找到。