对于软件系统来说,对运行在不同进程中的软件进行远程调用是很常见的,这些进程可能位于网络上的不同机器上。内存中调用和远程调用之间的一大区别是,远程调用可能会失败,或者在达到某个超时限制之前挂起而没有响应。更糟糕的是,如果您有许多呼叫者在没有响应的供应商上,那么您可能会耗尽关键资源,从而导致跨多个系统的级联故障。迈克尔·尼加德(Michael Nygard)在他的优秀著作“发布它”(Release It)中普及了断路器模式,以防止这种灾难性的级联。
断路器背后的基本思想非常简单。您将受保护的函数调用包装在监视故障的断路器对象中。一旦故障达到某个阈值,断路器就会跳闸,所有对断路器的进一步调用都会返回错误,而根本不会进行受保护的调用。通常,如果断路器跳闸,您还需要某种类型的监视器警报。
断路器存储块,初始化各种参数(阈值、超时和监控),并将断路器重置为闭合状态。
Attr_Accessor:INVOCATION_TIMEOUT,:FAILURE_THRESHOLD,:MONITOR定义初始化&;block@Circuit=block@invocation_timeout=0.01@FAILURE_THRESHOLD=5@MONITOR=ACCEPT_MONITOR重置。
如果电路闭合,则调用断路器将调用底层块,但如果电路断开,则返回错误。
定义调用参数案例状态条件:CLOSED BEGIN DO_CALL ARGS救援超时::ERROR RECORD_FAILURE RAY$!End When:OPEN然后引发断路器::OPEN ELSE RAISE";END END定义DO_CALL ARGS RESULT=TIMEOUT::TIMEOUT(@INVOCATION_TIMEOUT)DO@Circuit。调用参数END RESET RESET RETURN RESULT END。
如果超时,我们会递增失败计数器,成功的调用会将其重置为零。
DEF RECORD_FAILURE@FAILURE_COUNT+=1@monitor or.alert(:OPEN_CHECURE)如果:OPEN==状态结束def RESET@FAILURE_COUNT=0@monitor_.alert:RESET_CHECURE END
这个简单的断路器避免在电路打开时进行受保护的调用,但需要外部干预才能在情况恢复正常时将其重置。对于建筑物中的电气断路器,这是一种合理的方法,但对于软件断路器,我们可以让断路器本身检测底层呼叫是否再次工作。我们可以通过在适当的间隔后再次尝试受保护的调用,并在成功时重置断路器来实现此自重置行为。
创建这种断路器意味着添加尝试重置的阈值,并设置一个变量来保存最后一个错误的时间。
定义初始化&;block@Circuit=block@invocation_timeout=0.01@FAILURE_THRESHOLD=5@MONITOR=BreakerMonitor or.new@Reset_Timeout=0.1 RESET END def RESET@FAILURE_COUNT=0@LAST_FAILURE_TIME=nIL@monitor or.alert:RESET_CHECURE END。
现在有第三种状态存在-半开-这意味着电路准备进行真正的呼叫作为试验,看看问题是否得到解决。
定义状态大小写WHEN(@FAILURE_COUNT>;=@FAILURE_THRESHOLD)&;&;(time.Now-@LAST_FAILURE_TIME)>;@Reset_Timeout:Half_OPEN WHEN(@FAILURE_COUNT>;=@FAILURE_THRESHOLD):OPEN ELSE:闭合端。
要求在半开状态下调用会导致尝试调用,如果调用成功将重置断路器,否则将重新启动超时。
定义调用参数案例状态WHEN:CLOSED,:HARM_OPEN BEGIN DO_CALL ARGS救援超时::ERROR RECORD_FAILURE RAY$!结束时间:OPEN RAISE断路器::OPEN否则RAISE";READ";END END定义RECORD_FAILURE@FAILURE_COUNT+=1@LAST_FAILURE_TIME=time.Now@monitor or.alert(:OPEN_CRECTORE)IF:OPEN==STATE END。
此示例是一个简单的说明性示例,在实践中,断路器提供了更多的功能和参数化。它们通常会针对受保护呼叫可能引发的一系列错误(如网络连接故障)提供保护。并非所有错误都应跳闸电路,有些错误应反映正常故障,并作为常规逻辑的一部分进行处理。
在通信量很大的情况下,您可能会遇到许多呼叫仅仅等待初始超时的问题。由于远程调用通常很慢,因此使用将来将每个调用放在不同的线程上或承诺在结果返回时处理结果通常是个好主意。通过从线程池中提取这些线程,您可以安排线路在线程池耗尽时中断。
该示例显示了触发断路器的一种简单方法-成功调用时重置的计数。一种更复杂的方法可能会考虑错误的频率,比如说,一旦你获得50%的失败率,就会绊倒。对于不同的错误,您可能也有不同的阈值,例如,超时的阈值为10,连接失败的阈值为3。
我已经展示的示例是用于同步调用的断路器,但是断路器对于异步通信也很有用。这里的一种常见技术是将所有请求放在一个队列中,供应商以其速度使用该队列-这是避免服务器过载的有用技术。在这种情况下,当队列填满时电路中断。
断路器本身有助于减少可能失败的操作中占用的资源。您可以避免等待客户端的超时,中断的电路可以避免给陷入困境的服务器带来负载。我在这里讨论远程调用,这是断路器的常见情况,但它们可以用于任何您想要保护系统的各部分不受其他部分故障影响的情况。
断路器是一个有价值的监控场所。断路器状态的任何变化都应记录下来,断路器应显示其状态的详细信息,以便进行更深入的监控。断路器的行为通常是对环境中更深层次的问题发出警告的一个很好的来源。操作人员应能够跳闸或复位断路器。
断路器本身是有价值的,但使用它们的客户需要对断路器故障做出反应。与任何远程调用一样,您需要考虑在失败的情况下如何处理。您正在执行的操作失败了吗,或者您有什么解决办法吗?信用卡授权可以放在队列中稍后处理,通过显示一些足够好显示的陈旧数据可以减轻无法获取某些数据的问题。
Netflix技术博客包含了许多有用的信息,介绍如何通过大量服务提高系统的可靠性。他们的依赖命令谈到了使用断路器和线程池限制。
Netflix拥有开源的hystrix,这是一个处理分布式系统延迟和容错的复杂工具。它包括具有线程池限制的断路器模式的实现
Ruby、Java、Grails插件、C#、AspectJ和Scala中还有其他开放源码的断路器模式实现