- 浏览: 307082 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
wst0350:
点赞,有空深入讲解下原理
Servlet、Filter 和 Listener 调用顺序、生命周期的实验分析 -
tuspark:
Eclipse中高亮设置内容还有很多细节选项可以设置的,可以看 ...
Eclipse 设置匹配代码高亮 -
xichao1929:
这个时候,怎么启动发布的项目呢?????
JBoss设置为Windows服务 -
xiaozi7:
非常感谢,楼主的英语水平不一般那
WebSphere MQ Version 7 发布订阅相关配置 -
qtlkw:
slave没玩过
Hudson: java.lang.OutOfMemoryError: Java heap space error
转载: http://www.cnblogs.com/yangecnu/p/Introduce-Circuit-Breaker-Pattern.html
如果大家有印象的话,尤其是夏天,如果家里用电负载过大,比如开了很多家用电器,就会”自动跳闸”,此时电路就会断开。在以前更古老的一种方式是”保险丝”,当负载过大,或者电路发生故障或异常时,电流会不断升高,为防止升高的电流有可能损坏电路中的某些重要器件或贵重器件,烧毁电路甚至造成火灾。保险丝会在电流异常升高到一定的高度和热度的时候,自身熔断切断电流,从而起到保护电路安全运行的作用。
同样,在大型的软件系统中,如果调用的远程服务或者资源由于某种原因无法使用时,如果没有这种过载保护,就会导致请求的资源阻塞在服务器上等待从而耗尽系统或者服务器资源。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。软件系统中的这种过载保护就是本文将要谈到的熔断器模式(Circuit Breaker)
一 问题的产生
在大型的分布式系统中,通常需要调用或操作远程的服务或者资源,这些远程的服务或者资源由于调用者不可以控的原因比如网络连接缓慢,资源被占用或者暂时不可用等原因,导致对这些远程资源的调用失败。这些错误通常在稍后的一段时间内可以恢复正常。
但是,在某些情况下,由于一些无法预知的原因导致结果很难预料,远程的方法或者资源可能需要很长的一段时间才能修复。这种错误严重到系统的部分失去响应甚至导致整个服务的完全不可用。在这种情况下,采用不断地重试可能解决不了问题,相反,应用程序在这个时候应该立即返回并且报告错误。
通常,如果一个服务器非常繁忙,那么系统中的部分失败可能会导致 “连锁失效”(cascading failure)。比如,某个操作可能会调用一个远程的WebService,这个service会设置一个超时的时间,如果响应时间超过了该时间就会抛出一个异常。但是这种策略会导致并发的请求调用同样的操作会阻塞,一直等到超时时间的到期。这种对请求的阻塞可能会占用宝贵的系统资源,如内存,线程,数据库连接等等,最后这些资源就会消耗殆尽,使得其他系统不相关的部分所使用的资源也耗尽从而拖累整个系统。在这种情况下,操作立即返回错误而不是等待超时的发生可能是一种更好的选择。只有当调用服务有可能成功时我们再去尝试。
二 解决方法
熔断器模式可以防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器模式也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
熔断器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。
Circuit Breaker
熔断器可以使用状态机来实现,内部模拟以下几种状态。
闭合(closed)状态: 对应用程序的请求能够直接引起方法的调用。代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。
断开(Open)状态:在该状态下,对应用程序的请求会立即返回错误响应。
半断开(Half-Open)状态:允许对应用程序的一定数量的请求可以去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态(并且将错误计数器重置);如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开方式,然后开始重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。
各个状态之间的转换如下图:
Circuit Breaker State Change
在Close状态下,错误计数器是基于时间的。在特定的时间间隔内会自动重置。这能够防止由于某次的偶然错误导致熔断器进入断开状态。触发熔断器进入断开状态的失败阈值只有在特定的时间间隔内,错误次数达到指定错误次数的阈值才会产生。在Half-Open状态中使用的连续成功次数计数器记录调用的成功次数。当连续调用成功次数达到某个指定值时,切换到闭合状态,如果某次调用失败,立即切换到断开状态,连续成功调用次数计时器在下次进入半断开状态时归零。
实现熔断器模式使得系统更加稳定和有弹性,在系统从错误中恢复的时候提供稳定性,并且减少了错误对系统性能的影响。它通过快速的拒绝那些试图有可能调用会导致错误的服务,而不会去等待操作超时或者永远不会不返回结果来提高系统的响应事件。如果熔断器设计模式在每次状态切换的时候会发出一个事件,这种信息可以用来监控服务的运行状态,能够通知管理员在熔断器切换到断开状态时进行处理。
可以对熔断器模式进行定制以适应一些可能会导致远程服务失败的特定场景。比如,可以在熔断器中对超时时间使用不断增长的策略。在熔断器开始进入断开状态的时候,可以设置超时时间为几秒钟,然后如果错误没有被解决,然后将该超时时间设置为几分钟,依次类推。在一些情况下,在断开状态下我们可以返回一些错误的默认值,而不是抛出异常。
三 要考虑的因素
在实现熔断器模式的时候,以下这些因素需可能需要考虑:
异常处理:调用受熔断器保护的服务的时候,我们必须要处理当服务不可用时的异常情况。这些异常处理通常需要视具体的业务情况而定。比如,如果应用程序只是暂时的功能降级,可能需要切换到其它的可替换的服务上来执行相同的任务或者获取相同的数据,或者给用户报告错误然后提示他们稍后重试。
异常的类型:请求失败的原因可能有很多种。一些原因可能会比其它原因更严重。比如,请求会失败可能是由于远程的服务崩溃,这可能需要花费数分钟来恢复;也可能是由于服务器暂时负载过重导致超时。熔断器应该能够检查错误的类型,从而根据具体的错误情况来调整策略。比如,可能需要很多次超时异常才可以断定需要切换到断开状态,而只需要几次错误提示就可以判断服务不可用而快速切换到断开状态。
日志:熔断器应该能够记录所有失败的请求,以及一些可能会尝试成功的请求,使得的管理员能够监控使用熔断器保护的服务的执行情况。
测试服务是否可用:在断开状态下,熔断器可以采用定期的ping远程的服务或者资源,来判断是否服务是否恢复,而不是使用计时器来自动切换到半断开状态。这种ping操作可以模拟之前那些失败的请求,或者可以使用通过调用远程服务提供的检查服务是否可用的方法来判断。
手动重置:在系统中对于失败操作的恢复时间是很难确定的,提供一个手动重置功能能够使得管理员可以手动的强制将熔断器切换到闭合状态。同样的,如果受熔断器保护的服务暂时不可用的话,管理员能够强制的将熔断器设置为断开状态。
并发问题:相同的熔断器有可能被大量并发请求同时访问。熔断器的实现不应该阻塞并发的请求或者增加每次请求调用的负担。
资源的差异性:使用单个熔断器时,一个资源如果有分布在多个地方就需要小心。比如,一个数据可能存储在多个磁盘分区上(shard),某个分区可以正常访问,而另一个可能存在暂时性的问题。在这种情况下,不同的错误响应如果混为一谈,那么应用程序访问的这些存在问题的分区的失败的可能性就会高,而那些被认为是正常的分区,就有可能被阻塞。
加快熔断器的熔断操作:有时候,服务返回的错误信息足够让熔断器立即执行熔断操作并且保持一段时间。比如,如果从一个分布式资源返回的响应提示负载超重,那么可以断定出不建议立即重试,而是应该等待几分钟后再重试。(HTTP协议定义了”HTTP 503 Service Unavailable”来表示请求的服务当前不可用,他可以包含其他信息比如,超时等)
重复失败请求:当熔断器在断开状态的时候,熔断器可以记录每一次请求的细节,而不是仅仅返回失败信息,这样当远程服务恢复的时候,可以将这些失败的请求再重新请求一次。
四 使用场景
应该使用该模式来:
防止应用程序直接调用那些很可能会调用失败的远程服务或共享资源。
不适合的场景
对于应用程序中的直接访问本地私有资源,比如内存中的数据结构,如果使用熔断器模式只会增加系统额外开销。
不适合作为应用程序中业务逻辑的异常处理替代品
五 实现
根据上面的状态切换图,我们很容易实现一个基本的熔断器,只需要在内部维护一个状态机,并定义好状态转移的规则,可以使用State模式来实现。首先,我们定义一个表示状态转移操作的抽象类CircuitBreakerState:
public abstract class CircuitBreakerState
{
protected CircuitBreakerState(CircuitBreaker circuitBreaker)
{
this.circuitBreaker = circuitBreaker;
}
/// <summary>
/// 调用受保护方法之前处理的操作
/// </summary>
public virtual void ProtectedCodeIsAboutToBeCalled() {
//如果是断开状态,直接返回
//然后坐等超时转换到半断开状态
if (circuitBreaker.IsOpen)
{
throw new OpenCircuitException();
}
}
/// <summary>
/// 受熔断器保护的方法调用成功之后的操作
/// </summary>
public virtual void ProtectedCodeHasBeenCalled()
{
circuitBreaker.IncreaseSuccessCount();
}
/// <summary>
///受熔断器保护的方法调用发生异常操作后的操作
/// </summary>
/// <param name="e"></param>
public virtual void ActUponException(Exception e)
{
//增加失败次数计数器,并且保存错误信息
circuitBreaker.IncreaseFailureCount(e);
//重置连续成功次数
circuitBreaker.ResetConsecutiveSuccessCount();
}
protected readonly CircuitBreaker circuitBreaker;
}
抽象类中,状态机CircuitBreaker通过构造函数注入;当发生错误时,我们增加错误计数器,并且重置连续成功计数器,在增加错误计数器操作中,同时也记录了出错的异常信息。
然后在分别实现表示熔断器三个状态的类。首先实现闭合状态CloseState:
public class ClosedState : CircuitBreakerState
{
public ClosedState(CircuitBreaker circuitBreaker)
: base(circuitBreaker)
{
//重置失败计数器
circuitBreaker.ResetFailureCount();
}
public override void ActUponException(Exception e)
{
base.ActUponException(e);
//如果失败次数达到阈值,则切换到断开状态
if (circuitBreaker.FailureThresholdReached())
{
circuitBreaker.MoveToOpenState();
}
}
}
在闭合状态下,如果发生错误,并且错误次数达到阈值,则状态机切换到断开状态。断开状态OpenState的实现如下:
public class OpenState : CircuitBreakerState
{
private readonly Timer timer;
public OpenState(CircuitBreaker circuitBreaker)
: base(circuitBreaker)
{
timer = new Timer(circuitBreaker.Timeout.TotalMilliseconds);
timer.Elapsed += TimeoutHasBeenReached;
timer.AutoReset = false;
timer.Start();
}
//断开超过设定的阈值,自动切换到半断开状态
private void TimeoutHasBeenReached(object sender, ElapsedEventArgs e)
{
circuitBreaker.MoveToHalfOpenState();
}
public override void ProtectedCodeIsAboutToBeCalled()
{
base.ProtectedCodeIsAboutToBeCalled();
throw new OpenCircuitException();
}
}
断开状态内部维护一个计数器,如果断开达到一定的时间,则自动切换到版断开状态,并且,在断开状态下,如果需要执行操作,则直接抛出异常。
最后半断开Half-Open状态实现如下:
public class HalfOpenState : CircuitBreakerState
{
public HalfOpenState(CircuitBreaker circuitBreaker)
: base(circuitBreaker)
{
//重置连续成功计数
circuitBreaker.ResetConsecutiveSuccessCount();
}
public override void ActUponException(Exception e)
{
base.ActUponException(e);
//只要有失败,立即切换到断开模式
circuitBreaker.MoveToOpenState();
}
public override void ProtectedCodeHasBeenCalled()
{
base.ProtectedCodeHasBeenCalled();
//如果连续成功次数达到阈值,切换到闭合状态
if (circuitBreaker.ConsecutiveSuccessThresholdReached())
{
circuitBreaker.MoveToClosedState();
}
}
}
切换到半断开状态时,将连续成功调用计数重置为0,当执行成功的时候,自增改字段,当达到连读调用成功次数的阈值时,切换到闭合状态。如果调用失败,立即切换到断开模式。
有了以上三种状态切换之后,我们要实现CircuitBreaker类了:
public class CircuitBreaker
{
private readonly object monitor = new object();
private CircuitBreakerState state;
public int FailureCount { get; private set; }
public int ConsecutiveSuccessCount { get; private set; }
public int FailureThreshold { get; private set; }
public int ConsecutiveSuccessThreshold { get; private set; }
public TimeSpan Timeout { get; private set; }
public Exception LastException { get; private set; }
public bool IsClosed
{
get { return state is ClosedState; }
}
public bool IsOpen
{
get { return state is OpenState; }
}
public bool IsHalfOpen
{
get { return state is HalfOpenState; }
}
internal void MoveToClosedState()
{
state = new ClosedState(this);
}
internal void MoveToOpenState()
{
state = new OpenState(this);
}
internal void MoveToHalfOpenState()
{
state = new HalfOpenState(this);
}
internal void IncreaseFailureCount(Exception ex)
{
LastException = ex;
FailureCount++;
}
internal void ResetFailureCount()
{
FailureCount = 0;
}
internal bool FailureThresholdReached()
{
return FailureCount >= FailureThreshold;
}
internal void IncreaseSuccessCount()
{
ConsecutiveSuccessCount++;
}
internal void ResetConsecutiveSuccessCount()
{
ConsecutiveSuccessCount = 0;
}
internal bool ConsecutiveSuccessThresholdReached()
{
return ConsecutiveSuccessCount >= ConsecutiveSuccessThreshold;
}
}
在该类中首先:
定义了一些记录状态的变量,如FailureCount,ConsecutiveSuccessCount 记录失败次数,连续成功次数,以及FailureThreshold,ConsecutiveSuccessThreshold记录最大调用失败次数,连续调用成功次数。这些对象对外部来说是只读的。
定义了一个 CircuitBreakerState类型的state变量,以表示当前系统的状态。
定义了一些列获取当前状态的方法IsOpen,IsClose,IsHalfOpen,以及表示状态转移的方法MoveToOpenState,MoveToClosedState等,这些方法比较简单,根据名字即可看出用意。
然后,可以通过构造函数将在Close状态下最大失败次数,HalfOpen状态下使用的最大连续成功次数,以及Open状态下的超时时间通过构造函数传进来:
public CircuitBreaker(int failedthreshold, int consecutiveSuccessThreshold, TimeSpan timeout)
{
if (failedthreshold < 1 || consecutiveSuccessThreshold < 1)
{
throw new ArgumentOutOfRangeException("threshold", "Threshold should be greater than 0");
}
if (timeout.TotalMilliseconds < 1)
{
throw new ArgumentOutOfRangeException("timeout", "Timeout should be greater than 0");
}
FailureThreshold = failedthreshold;
ConsecutiveSuccessThreshold = consecutiveSuccessThreshold;
Timeout = timeout;
MoveToClosedState();
}
在初始状态下,熔断器切换到闭合状态。
然后,可以通过AttempCall调用,传入期望执行的代理方法,该方法的执行受熔断器保护。这里使用了锁来处理并发问题。
public void AttemptCall(Action protectedCode)
{
using (TimedLock.Lock(monitor))
{
state.ProtectedCodeIsAboutToBeCalled();
}
try
{
protectedCode();
}
catch (Exception e)
{
using (TimedLock.Lock(monitor))
{
state.ActUponException(e);
}
throw;
}
using (TimedLock.Lock(monitor))
{
state.ProtectedCodeHasBeenCalled();
}
}
最后,提供Close和Open两个方法来手动切换当前状态。
public void Close()
{
using (TimedLock.Lock(monitor))
{
MoveToClosedState();
}
}
public void Open()
{
using (TimedLock.Lock(monitor))
{
MoveToOpenState();
}
}
六 测试
以上的熔断模式,我们可以对其建立单元测试。
首先我们编写几个帮助类以模拟连续执行次数:
private static void CallXAmountOfTimes(Action codeToCall, int timesToCall)
{
for (int i = 0; i < timesToCall; i++)
{
codeToCall();
}
}
以下类用来抛出特定异常:
private static void AssertThatExceptionIsThrown<T>(Action code) where T : Exception
{
try
{
code();
}
catch (T)
{
return;
}
Assert.Fail("Expected exception of type {0} was not thrown", typeof(T).FullName);
}
然后,使用NUnit,可以建立如下Case:
[Test]
public void ClosesIfProtectedCodeSucceedsInHalfOpenState()
{
var stub = new Stub(10);
//定义熔断器,失败10次进入断开状态
//5秒后进入半断开状态
//在半断开状态下,连续成功15次,进入闭合状态
var circuitBreaker = new CircuitBreaker(10, 15, TimeSpan.FromMilliseconds(5000));
Assert.That(circuitBreaker.IsClosed);
//失败10次调用
CallXAmountOfTimes(() => AssertThatExceptionIsThrown<ApplicationException>(() => circuitBreaker.AttemptCall(stub.DoStuff)), 10);
Assert.AreEqual(10, circuitBreaker.FailureCount);
Assert.That(circuitBreaker.IsOpen);
//等待从Open转到HalfOpen
Thread.Sleep(6000);
Assert.That(circuitBreaker.IsHalfOpen);
//成功调用15次
CallXAmountOfTimes(()=>circuitBreaker.AttemptCall(stub.DoStuff), 15);
Assert.AreEqual(15, circuitBreaker.ConsecutiveSuccessCount);
Assert.AreEqual(0, circuitBreaker.FailureCount);
Assert.That(circuitBreaker.IsClosed);
}
这个Case模拟了熔断器中状态的转换。首先初始化时,熔断器处于闭合状态,然后连续10次调用抛出异常,这时熔断器进去了断开状态,然后让线程等待6秒,此时在第5秒的时候,状态切换到了半断开状态。然后连续15次成功调用,此时状态又切换到了闭合状态。
七 结论
在应用系统中,我们通常会去调用远程的服务或者资源(这些服务或资源通常是来自第三方),对这些远程服务或者资源的调用通常会导致失败,或者挂起没有响应,直到超时的产生。在一些极端情况下,大量的请求会阻塞在对这些异常的远程服务的调用上,会导致一些关键性的系统资源耗尽,从而导致级联的失败,从而拖垮整个系统。熔断器模式在内部采用状态机的形式,使得对这些可能会导致请求失败的远程服务进行了包装,当远程服务发生异常时,可以立即对进来的请求返回错误响应,并告知系统管理员,将错误控制在局部范围内,从而提高系统的稳定性和可靠性。
本文首先介绍了熔断器模式使用的场景,能够解决的问题,以及需要考虑的因素,最后使用代码展示了如何实现一个简单的熔断器,并且给出了测试用例,希望这些对您有帮助,尤其是在当您的系统调用了外部的远程服务或者资源,同时访问量又很大的情况下对提高系统的稳定性和可靠性有所帮助。
如果大家有印象的话,尤其是夏天,如果家里用电负载过大,比如开了很多家用电器,就会”自动跳闸”,此时电路就会断开。在以前更古老的一种方式是”保险丝”,当负载过大,或者电路发生故障或异常时,电流会不断升高,为防止升高的电流有可能损坏电路中的某些重要器件或贵重器件,烧毁电路甚至造成火灾。保险丝会在电流异常升高到一定的高度和热度的时候,自身熔断切断电流,从而起到保护电路安全运行的作用。
同样,在大型的软件系统中,如果调用的远程服务或者资源由于某种原因无法使用时,如果没有这种过载保护,就会导致请求的资源阻塞在服务器上等待从而耗尽系统或者服务器资源。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。软件系统中的这种过载保护就是本文将要谈到的熔断器模式(Circuit Breaker)
一 问题的产生
在大型的分布式系统中,通常需要调用或操作远程的服务或者资源,这些远程的服务或者资源由于调用者不可以控的原因比如网络连接缓慢,资源被占用或者暂时不可用等原因,导致对这些远程资源的调用失败。这些错误通常在稍后的一段时间内可以恢复正常。
但是,在某些情况下,由于一些无法预知的原因导致结果很难预料,远程的方法或者资源可能需要很长的一段时间才能修复。这种错误严重到系统的部分失去响应甚至导致整个服务的完全不可用。在这种情况下,采用不断地重试可能解决不了问题,相反,应用程序在这个时候应该立即返回并且报告错误。
通常,如果一个服务器非常繁忙,那么系统中的部分失败可能会导致 “连锁失效”(cascading failure)。比如,某个操作可能会调用一个远程的WebService,这个service会设置一个超时的时间,如果响应时间超过了该时间就会抛出一个异常。但是这种策略会导致并发的请求调用同样的操作会阻塞,一直等到超时时间的到期。这种对请求的阻塞可能会占用宝贵的系统资源,如内存,线程,数据库连接等等,最后这些资源就会消耗殆尽,使得其他系统不相关的部分所使用的资源也耗尽从而拖累整个系统。在这种情况下,操作立即返回错误而不是等待超时的发生可能是一种更好的选择。只有当调用服务有可能成功时我们再去尝试。
二 解决方法
熔断器模式可以防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器模式也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
熔断器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。
Circuit Breaker
熔断器可以使用状态机来实现,内部模拟以下几种状态。
闭合(closed)状态: 对应用程序的请求能够直接引起方法的调用。代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。
断开(Open)状态:在该状态下,对应用程序的请求会立即返回错误响应。
半断开(Half-Open)状态:允许对应用程序的一定数量的请求可以去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态(并且将错误计数器重置);如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开方式,然后开始重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。
各个状态之间的转换如下图:
Circuit Breaker State Change
在Close状态下,错误计数器是基于时间的。在特定的时间间隔内会自动重置。这能够防止由于某次的偶然错误导致熔断器进入断开状态。触发熔断器进入断开状态的失败阈值只有在特定的时间间隔内,错误次数达到指定错误次数的阈值才会产生。在Half-Open状态中使用的连续成功次数计数器记录调用的成功次数。当连续调用成功次数达到某个指定值时,切换到闭合状态,如果某次调用失败,立即切换到断开状态,连续成功调用次数计时器在下次进入半断开状态时归零。
实现熔断器模式使得系统更加稳定和有弹性,在系统从错误中恢复的时候提供稳定性,并且减少了错误对系统性能的影响。它通过快速的拒绝那些试图有可能调用会导致错误的服务,而不会去等待操作超时或者永远不会不返回结果来提高系统的响应事件。如果熔断器设计模式在每次状态切换的时候会发出一个事件,这种信息可以用来监控服务的运行状态,能够通知管理员在熔断器切换到断开状态时进行处理。
可以对熔断器模式进行定制以适应一些可能会导致远程服务失败的特定场景。比如,可以在熔断器中对超时时间使用不断增长的策略。在熔断器开始进入断开状态的时候,可以设置超时时间为几秒钟,然后如果错误没有被解决,然后将该超时时间设置为几分钟,依次类推。在一些情况下,在断开状态下我们可以返回一些错误的默认值,而不是抛出异常。
三 要考虑的因素
在实现熔断器模式的时候,以下这些因素需可能需要考虑:
异常处理:调用受熔断器保护的服务的时候,我们必须要处理当服务不可用时的异常情况。这些异常处理通常需要视具体的业务情况而定。比如,如果应用程序只是暂时的功能降级,可能需要切换到其它的可替换的服务上来执行相同的任务或者获取相同的数据,或者给用户报告错误然后提示他们稍后重试。
异常的类型:请求失败的原因可能有很多种。一些原因可能会比其它原因更严重。比如,请求会失败可能是由于远程的服务崩溃,这可能需要花费数分钟来恢复;也可能是由于服务器暂时负载过重导致超时。熔断器应该能够检查错误的类型,从而根据具体的错误情况来调整策略。比如,可能需要很多次超时异常才可以断定需要切换到断开状态,而只需要几次错误提示就可以判断服务不可用而快速切换到断开状态。
日志:熔断器应该能够记录所有失败的请求,以及一些可能会尝试成功的请求,使得的管理员能够监控使用熔断器保护的服务的执行情况。
测试服务是否可用:在断开状态下,熔断器可以采用定期的ping远程的服务或者资源,来判断是否服务是否恢复,而不是使用计时器来自动切换到半断开状态。这种ping操作可以模拟之前那些失败的请求,或者可以使用通过调用远程服务提供的检查服务是否可用的方法来判断。
手动重置:在系统中对于失败操作的恢复时间是很难确定的,提供一个手动重置功能能够使得管理员可以手动的强制将熔断器切换到闭合状态。同样的,如果受熔断器保护的服务暂时不可用的话,管理员能够强制的将熔断器设置为断开状态。
并发问题:相同的熔断器有可能被大量并发请求同时访问。熔断器的实现不应该阻塞并发的请求或者增加每次请求调用的负担。
资源的差异性:使用单个熔断器时,一个资源如果有分布在多个地方就需要小心。比如,一个数据可能存储在多个磁盘分区上(shard),某个分区可以正常访问,而另一个可能存在暂时性的问题。在这种情况下,不同的错误响应如果混为一谈,那么应用程序访问的这些存在问题的分区的失败的可能性就会高,而那些被认为是正常的分区,就有可能被阻塞。
加快熔断器的熔断操作:有时候,服务返回的错误信息足够让熔断器立即执行熔断操作并且保持一段时间。比如,如果从一个分布式资源返回的响应提示负载超重,那么可以断定出不建议立即重试,而是应该等待几分钟后再重试。(HTTP协议定义了”HTTP 503 Service Unavailable”来表示请求的服务当前不可用,他可以包含其他信息比如,超时等)
重复失败请求:当熔断器在断开状态的时候,熔断器可以记录每一次请求的细节,而不是仅仅返回失败信息,这样当远程服务恢复的时候,可以将这些失败的请求再重新请求一次。
四 使用场景
应该使用该模式来:
防止应用程序直接调用那些很可能会调用失败的远程服务或共享资源。
不适合的场景
对于应用程序中的直接访问本地私有资源,比如内存中的数据结构,如果使用熔断器模式只会增加系统额外开销。
不适合作为应用程序中业务逻辑的异常处理替代品
五 实现
根据上面的状态切换图,我们很容易实现一个基本的熔断器,只需要在内部维护一个状态机,并定义好状态转移的规则,可以使用State模式来实现。首先,我们定义一个表示状态转移操作的抽象类CircuitBreakerState:
public abstract class CircuitBreakerState
{
protected CircuitBreakerState(CircuitBreaker circuitBreaker)
{
this.circuitBreaker = circuitBreaker;
}
/// <summary>
/// 调用受保护方法之前处理的操作
/// </summary>
public virtual void ProtectedCodeIsAboutToBeCalled() {
//如果是断开状态,直接返回
//然后坐等超时转换到半断开状态
if (circuitBreaker.IsOpen)
{
throw new OpenCircuitException();
}
}
/// <summary>
/// 受熔断器保护的方法调用成功之后的操作
/// </summary>
public virtual void ProtectedCodeHasBeenCalled()
{
circuitBreaker.IncreaseSuccessCount();
}
/// <summary>
///受熔断器保护的方法调用发生异常操作后的操作
/// </summary>
/// <param name="e"></param>
public virtual void ActUponException(Exception e)
{
//增加失败次数计数器,并且保存错误信息
circuitBreaker.IncreaseFailureCount(e);
//重置连续成功次数
circuitBreaker.ResetConsecutiveSuccessCount();
}
protected readonly CircuitBreaker circuitBreaker;
}
抽象类中,状态机CircuitBreaker通过构造函数注入;当发生错误时,我们增加错误计数器,并且重置连续成功计数器,在增加错误计数器操作中,同时也记录了出错的异常信息。
然后在分别实现表示熔断器三个状态的类。首先实现闭合状态CloseState:
public class ClosedState : CircuitBreakerState
{
public ClosedState(CircuitBreaker circuitBreaker)
: base(circuitBreaker)
{
//重置失败计数器
circuitBreaker.ResetFailureCount();
}
public override void ActUponException(Exception e)
{
base.ActUponException(e);
//如果失败次数达到阈值,则切换到断开状态
if (circuitBreaker.FailureThresholdReached())
{
circuitBreaker.MoveToOpenState();
}
}
}
在闭合状态下,如果发生错误,并且错误次数达到阈值,则状态机切换到断开状态。断开状态OpenState的实现如下:
public class OpenState : CircuitBreakerState
{
private readonly Timer timer;
public OpenState(CircuitBreaker circuitBreaker)
: base(circuitBreaker)
{
timer = new Timer(circuitBreaker.Timeout.TotalMilliseconds);
timer.Elapsed += TimeoutHasBeenReached;
timer.AutoReset = false;
timer.Start();
}
//断开超过设定的阈值,自动切换到半断开状态
private void TimeoutHasBeenReached(object sender, ElapsedEventArgs e)
{
circuitBreaker.MoveToHalfOpenState();
}
public override void ProtectedCodeIsAboutToBeCalled()
{
base.ProtectedCodeIsAboutToBeCalled();
throw new OpenCircuitException();
}
}
断开状态内部维护一个计数器,如果断开达到一定的时间,则自动切换到版断开状态,并且,在断开状态下,如果需要执行操作,则直接抛出异常。
最后半断开Half-Open状态实现如下:
public class HalfOpenState : CircuitBreakerState
{
public HalfOpenState(CircuitBreaker circuitBreaker)
: base(circuitBreaker)
{
//重置连续成功计数
circuitBreaker.ResetConsecutiveSuccessCount();
}
public override void ActUponException(Exception e)
{
base.ActUponException(e);
//只要有失败,立即切换到断开模式
circuitBreaker.MoveToOpenState();
}
public override void ProtectedCodeHasBeenCalled()
{
base.ProtectedCodeHasBeenCalled();
//如果连续成功次数达到阈值,切换到闭合状态
if (circuitBreaker.ConsecutiveSuccessThresholdReached())
{
circuitBreaker.MoveToClosedState();
}
}
}
切换到半断开状态时,将连续成功调用计数重置为0,当执行成功的时候,自增改字段,当达到连读调用成功次数的阈值时,切换到闭合状态。如果调用失败,立即切换到断开模式。
有了以上三种状态切换之后,我们要实现CircuitBreaker类了:
public class CircuitBreaker
{
private readonly object monitor = new object();
private CircuitBreakerState state;
public int FailureCount { get; private set; }
public int ConsecutiveSuccessCount { get; private set; }
public int FailureThreshold { get; private set; }
public int ConsecutiveSuccessThreshold { get; private set; }
public TimeSpan Timeout { get; private set; }
public Exception LastException { get; private set; }
public bool IsClosed
{
get { return state is ClosedState; }
}
public bool IsOpen
{
get { return state is OpenState; }
}
public bool IsHalfOpen
{
get { return state is HalfOpenState; }
}
internal void MoveToClosedState()
{
state = new ClosedState(this);
}
internal void MoveToOpenState()
{
state = new OpenState(this);
}
internal void MoveToHalfOpenState()
{
state = new HalfOpenState(this);
}
internal void IncreaseFailureCount(Exception ex)
{
LastException = ex;
FailureCount++;
}
internal void ResetFailureCount()
{
FailureCount = 0;
}
internal bool FailureThresholdReached()
{
return FailureCount >= FailureThreshold;
}
internal void IncreaseSuccessCount()
{
ConsecutiveSuccessCount++;
}
internal void ResetConsecutiveSuccessCount()
{
ConsecutiveSuccessCount = 0;
}
internal bool ConsecutiveSuccessThresholdReached()
{
return ConsecutiveSuccessCount >= ConsecutiveSuccessThreshold;
}
}
在该类中首先:
定义了一些记录状态的变量,如FailureCount,ConsecutiveSuccessCount 记录失败次数,连续成功次数,以及FailureThreshold,ConsecutiveSuccessThreshold记录最大调用失败次数,连续调用成功次数。这些对象对外部来说是只读的。
定义了一个 CircuitBreakerState类型的state变量,以表示当前系统的状态。
定义了一些列获取当前状态的方法IsOpen,IsClose,IsHalfOpen,以及表示状态转移的方法MoveToOpenState,MoveToClosedState等,这些方法比较简单,根据名字即可看出用意。
然后,可以通过构造函数将在Close状态下最大失败次数,HalfOpen状态下使用的最大连续成功次数,以及Open状态下的超时时间通过构造函数传进来:
public CircuitBreaker(int failedthreshold, int consecutiveSuccessThreshold, TimeSpan timeout)
{
if (failedthreshold < 1 || consecutiveSuccessThreshold < 1)
{
throw new ArgumentOutOfRangeException("threshold", "Threshold should be greater than 0");
}
if (timeout.TotalMilliseconds < 1)
{
throw new ArgumentOutOfRangeException("timeout", "Timeout should be greater than 0");
}
FailureThreshold = failedthreshold;
ConsecutiveSuccessThreshold = consecutiveSuccessThreshold;
Timeout = timeout;
MoveToClosedState();
}
在初始状态下,熔断器切换到闭合状态。
然后,可以通过AttempCall调用,传入期望执行的代理方法,该方法的执行受熔断器保护。这里使用了锁来处理并发问题。
public void AttemptCall(Action protectedCode)
{
using (TimedLock.Lock(monitor))
{
state.ProtectedCodeIsAboutToBeCalled();
}
try
{
protectedCode();
}
catch (Exception e)
{
using (TimedLock.Lock(monitor))
{
state.ActUponException(e);
}
throw;
}
using (TimedLock.Lock(monitor))
{
state.ProtectedCodeHasBeenCalled();
}
}
最后,提供Close和Open两个方法来手动切换当前状态。
public void Close()
{
using (TimedLock.Lock(monitor))
{
MoveToClosedState();
}
}
public void Open()
{
using (TimedLock.Lock(monitor))
{
MoveToOpenState();
}
}
六 测试
以上的熔断模式,我们可以对其建立单元测试。
首先我们编写几个帮助类以模拟连续执行次数:
private static void CallXAmountOfTimes(Action codeToCall, int timesToCall)
{
for (int i = 0; i < timesToCall; i++)
{
codeToCall();
}
}
以下类用来抛出特定异常:
private static void AssertThatExceptionIsThrown<T>(Action code) where T : Exception
{
try
{
code();
}
catch (T)
{
return;
}
Assert.Fail("Expected exception of type {0} was not thrown", typeof(T).FullName);
}
然后,使用NUnit,可以建立如下Case:
[Test]
public void ClosesIfProtectedCodeSucceedsInHalfOpenState()
{
var stub = new Stub(10);
//定义熔断器,失败10次进入断开状态
//5秒后进入半断开状态
//在半断开状态下,连续成功15次,进入闭合状态
var circuitBreaker = new CircuitBreaker(10, 15, TimeSpan.FromMilliseconds(5000));
Assert.That(circuitBreaker.IsClosed);
//失败10次调用
CallXAmountOfTimes(() => AssertThatExceptionIsThrown<ApplicationException>(() => circuitBreaker.AttemptCall(stub.DoStuff)), 10);
Assert.AreEqual(10, circuitBreaker.FailureCount);
Assert.That(circuitBreaker.IsOpen);
//等待从Open转到HalfOpen
Thread.Sleep(6000);
Assert.That(circuitBreaker.IsHalfOpen);
//成功调用15次
CallXAmountOfTimes(()=>circuitBreaker.AttemptCall(stub.DoStuff), 15);
Assert.AreEqual(15, circuitBreaker.ConsecutiveSuccessCount);
Assert.AreEqual(0, circuitBreaker.FailureCount);
Assert.That(circuitBreaker.IsClosed);
}
这个Case模拟了熔断器中状态的转换。首先初始化时,熔断器处于闭合状态,然后连续10次调用抛出异常,这时熔断器进去了断开状态,然后让线程等待6秒,此时在第5秒的时候,状态切换到了半断开状态。然后连续15次成功调用,此时状态又切换到了闭合状态。
七 结论
在应用系统中,我们通常会去调用远程的服务或者资源(这些服务或资源通常是来自第三方),对这些远程服务或者资源的调用通常会导致失败,或者挂起没有响应,直到超时的产生。在一些极端情况下,大量的请求会阻塞在对这些异常的远程服务的调用上,会导致一些关键性的系统资源耗尽,从而导致级联的失败,从而拖垮整个系统。熔断器模式在内部采用状态机的形式,使得对这些可能会导致请求失败的远程服务进行了包装,当远程服务发生异常时,可以立即对进来的请求返回错误响应,并告知系统管理员,将错误控制在局部范围内,从而提高系统的稳定性和可靠性。
本文首先介绍了熔断器模式使用的场景,能够解决的问题,以及需要考虑的因素,最后使用代码展示了如何实现一个简单的熔断器,并且给出了测试用例,希望这些对您有帮助,尤其是在当您的系统调用了外部的远程服务或者资源,同时访问量又很大的情况下对提高系统的稳定性和可靠性有所帮助。
发表评论
-
es使用两种方式
2018-06-28 16:26 0第一种方式: 使用TransportClient packag ... -
hbase
2018-06-25 13:50 0package cn.com.duiba.appordersy ... -
guava
2017-09-22 18:03 6381.Guava Cache的get/getIfPresent方 ... -
转:架构
2017-06-23 08:13 493架构是软件的核心和灵魂,没有好的架构的软件经过一段时间的迭代后 ... -
使用 redis 减少 秒杀库存 超卖思路
2017-06-22 23:58 561612月份重构公司社群活动产品,原来自己不是很成熟,按传统的形式 ... -
经典笔试题
2017-06-21 23:30 495public class BaseTest { pu ... -
Restful vs RPC
2017-01-23 10:54 865传统的RPC一般是基于二 ... -
自动产生随机数
2016-11-11 10:54 555/** * java生成随机数字和字母组合 ... -
commons-lang常用工具类StringEscapeUtils
2016-11-10 20:12 8831.escapeSql 提供sql转移功能,防止sql注入攻击 ... -
Java8:Lambda表达式增强版Comparator和排序
2016-10-27 10:32 2693http://www.importnew.com/15259. ... -
Java序列化几点注意事项
2016-10-26 17:02 894静态变量不属于对象,属于类,不能被序列化.还有瞬态的变量也不能 ... -
Rest vs dubbo
2016-09-15 18:10 0Rest 基于http, 大数据量和安全性可能不佳 dubbo ... -
List删除element两种方式的不同
2016-07-26 12:41 679public class DateUtilTest { ... -
Xmemcached——比spymemcached更快
2016-07-18 10:23 466Xmemcached是一个高性能的 ... -
velocity 缓存设置
2016-07-04 20:54 1062#velocity 是否开启缓存 velocity.resou ... -
Java8 Stream用法
2016-07-04 18:58 01. collect(toList()) 由stream里的值 ... -
Date 参数
2016-04-22 21:44 562Oracle TO_CHAR parameters: The ... -
Dubbo安装部署
2016-04-18 01:16 1608Jdk-1.6.30以上版本 Tomcat-7 ... -
java read也需要加锁
2016-02-27 18:11 643今天被问到read需不需要加锁,结果没答上来。自己写了一个程序 ... -
servicemix Permgen issue
2015-11-18 12:41 328In Java 6 the memory in Permgen ...
相关推荐
导致熔断器的熔体发热而融化,熔断器的短路是由于熔断器的装配不正确或熔断器的故障,导致熔断器短路,熔断器的过流是由于熔断器的额定电流超出设计值,导致熔断器的熔体发热而融化,熔断器的电弧是由于熔断器的电弧...
当熔断器FU1完好时,二极管VD1正向导通,其正向电压不足以激活自闪烁发光二极管VD2和晶体管VT1,因此,蜂鸣器B保持静默,电路处于监视待机模式。 一旦熔断器FU1因过载或短路而断开,负载电源路径被中断,VD1将截止...
这时,熔断器模式作为一种容错机制应运而生,它允许系统在面对故障时能够快速恢复,而不是持续等待或尝试修复。Netflix的开源库Hystrix就是实现熔断器模式的一个强大工具,它为Java开发者提供了强大的服务降级、服务...
在进行选型时,FAE工程师需要全面了解设备的工作条件、预期负载变化和故障模式,结合熔断器和PTC器件的性能特点,进行综合评估。此外,还需要考虑系统的整体设计,包括电压等级、电流需求、环境温度以及安全标准,以...
熔断器模式源自电路设计,当电流超过一定阈值时,电路会自动断开以保护系统不受损害。在软件系统中,Hystrix熔断器类似,当服务调用持续失败达到一定阈值时,它会“熔断”,停止调用,转而执行备选策略,如返回...
* 熔断器检测系统的设计需要考虑到标准化和通用性,以便于设备之间的互操作性。 六、熔断器检测系统的应用 * 熔断器检测系统广泛应用于电网保护和用电设备保护领域。 * 熔断器检测系统可以提高生产效率和自动化...
选择合适的高压熔断器需要考虑多个因素,包括熔断器的设计思路、选用方法、参数修正以及负载电流的计算。 首先,设计思路应确保熔断器能够在电动车高压系统中有效地工作。这通常涉及到熔断器的材料选择,如采用高...
因此,设计一款能够提供声光提醒功能的熔断器监控电路显得尤为重要。 本文将介绍一种具有声光提醒功能的熔断器监控电路的设计原理及其应用。该监控电路能够实时监测熔断器的状态,当熔断器因故障熔断时,电路能够...
- 通过训练机器学习模型识别出熔断器运行数据中的异常模式,进而判断出可能存在的故障类型及其根本原因。 - 该过程有助于运维人员更快速地响应异常情况,防止小问题演变成大故障。 2. **利用先进预测分析技术预测...
《高压熔断器行业(2021-2026)企业市场突围战略分析与建议》报告深入探讨了高压熔断器企业在复杂市场环境中如何制定有效策略,实现市场突破。报告首先强调了树立“战略突破”的重要性,认为企业应明确自身市场定位、...
本篇学习笔记将聚焦于在使用RestTemplate和Ribbon时如何集成和应用熔断器机制,以提高微服务的容错性和稳定性。 首先,Spring Cloud为微服务提供了强大的支持,其中Hystrix是Netflix开源的一个用于处理服务间的延迟...
在这个领域,户外柱上负荷开关与跌落熔断器组合电器是电力系统中的重要设备,尤其对于保障城市供电安全和稳定性起着至关重要的作用。 负荷开关是一种能开断和关合正常工作电流,但不能开断短路电流的开关设备。在...
深入理解带熔断器的三相电压互感器,需要掌握其设计理念、结构特点以及工作模式。设计时,要考虑到互感器的电气性能,如额定电压、额定电流、准确度等级、热稳定性和动稳定性。结构上,通常由铁芯、初级和次级绕组、...
2. **熔断器模式**:Hystrix 实现了一种熔断器开关机制,当服务的错误率达到一定阈值时,熔断器会自动开启,阻止新的请求发送到故障的服务,直到服务恢复正常。 3. **快速失败**:当服务出现故障时,Hystrix 会立即...
熔断器广泛应用于高低压配电系统、控制系统以及各种用电设备中,因此,在电气CAD设计中,准确绘制熔断器符号是一项基础且重要的任务。 在绘制熔断器符号时,“移动”命令的运用是提高绘图效率和准确性的关键。使用...
MSP430单片机的加密措施主要依赖于内部的加密熔断器。当JTAG接口的4根信号线(TCK、TDI、TDO、TMS)中的保险丝被熔断后,单片机便无法再通过JTAG接口进行访问,从而达到保护程序代码的目的。在上电复位时,单片机会...
- **Hystrix**:熔断器设计模式的实现,用于防止服务雪崩效应。当调用链中的某个服务出现故障时,Hystrix会快速失败,切换到降级策略,避免故障扩散。 - **Ribbon**:客户端负载均衡器,它集成在服务消费者中,...
总的来说,MSP430系列单片机的加密熔断器设计是其安全特性的重要组成部分,旨在保护用户的知识产权免受非法复制和窃取。通过理解和掌握这一加密机制,开发者能够在保障代码安全的同时,充分利用MSP430的强大功能进行...