最近在做分布式服务熔断,因为要实现一个熔断器状态机,所以想到状态模式。状态模式是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

先举个简单的例子,以红绿灯模型,说明状态模式是怎么一回事:

通常情况下我们是这样实现的:

public class TrafficLight {
private static enum State {
RED, GREEN, YELLOW
} private static State state = State.RED; public static void change() {
switch (state) {
case RED:
System.out.println("--------\n红灯(5s)");
sleep(5000);
state = State.GREEN;
break;
case GREEN:
System.out.println("绿灯(5s)");
sleep(5000);
state = State.YELLOW;
break;
case YELLOW:
System.out.println("黄灯(2s)");
sleep(2000);
state = State.RED;
}
} private static void sleep(int second) {
try {
Thread.sleep(second);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
while (true) {
TrafficLight.change();
}
}
}

输出:

以上这种写法,当增加一种灯控逻辑的时候就需要再添加一个分支,导致我们需要不断的去修改代码逻辑,下面使用状态模式实现:

定义状态接口

public interface LightState {
void showLight(LightManager manager, LightState nextState);
}

我们希望显示灯亮的时候程序休眠几秒,但我们不希望在LightState的每个实现类里面去定义一个sleep方法,所以我们定义一个抽象类Light来实现上面的接口

public abstract class Light implements LightState {

    protected void sleep(int second) {
try {
Thread.sleep(second);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

然后下面分别写LightState接口的三个实现类

public class RedLight extends Light {

    @Override
public void showLight(LightManager manager, LightState nextState) {
System.out.println("---------\n红灯!(5s)");
sleep(5000);
manager.setLightState(nextState);
}
}
public class GreenLight extends Light {

    @Override
public void showLight(LightManager manager, LightState nextState) {
System.out.println("绿灯!(5s)");
sleep(5000);
manager.setLightState(nextState);
}
}
public class YellowLight extends Light {

    @Override
public void showLight(LightManager manager, LightState nextState) {
System.out.println("黄灯!(2s)");
sleep(2000);
manager.setLightState(nextState);
}
}

接下来我们要实现一个灯控的管理类

public class LightManager {

    private LightState lightState;

    public LightManager(LightState lightState) {
this.lightState = lightState;
} public void setLightState(LightState lightState) {
this.lightState = lightState;
} public void changeLight(LightState nextState) {
lightState.showLight(this, nextState);
}
}

测试类

public class Test {
public static void main(String[] args) { LightState[] states = {new RedLight(), new GreenLight(), new YellowLight()}; int index = 0;
LightManager manager = new LightManager(states[index++]);
while (true) {
manager.changeLight(states[index++]);
if (index == states.length)
index = 0;
}
}
}

输出结果:

这样如果后面我们需要新增一个灯控颜色的比如蓝色的话,只需要实现LightState的实现类接口,不用修改到代码逻辑。

----------------------------------------------熔断器实现------------------------------------------------

接下来用状态模式实现下熔断器

整个状态机的逻辑大致如下:

  服务熔断主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。熔断器可以使用状态机来实现,内部模拟以下几种状态。

  • 闭合(Closed)状态: 对应用程序的请求能够直接引起方法的调用。代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。
  • 断开(Open)状态:在该状态下,对应用程序的请求会立即返回错误响应。
  • 半断开(Half-Open)状态:允许对应用程序的一定数量的请求可以去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态(并且将错误计数器重置);如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开方式,然后开始重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。

  熔断器核心功能实际上是维护一个状态机,并定义好状态转移的规则,这里定义一个状态转移操作的抽象类AbstractBreakerState,状态机的抽象类图如下:

/**
* 熔断器状态转移操作的抽象类
*/
public abstract class AbstractBreakerState { protected BreakerManager manager; public AbstractBreakerState(BreakerManager manager) {
this.manager = manager;
} /**
* 调用方法之前处理的操作
*/
public void protectedCodeIsAboutToBeCalled() {
//如果是断开状态,直接返回,然后等超时转换到半断开状态
if (manager.isOpen()) {
throw new RuntimeException("服务已熔断,请稍等重试!");
}
} /**
* 方法调用成功之后的操作
*/
public void protectedCodeHasBeenCalled() {
manager.increaseSuccessCount();
} /**
* 方法调用发生异常操作后的操作
*/
public void ActUponException() {
//增加失败次数计数器,并且保存错误信息
manager.increaseFailureCount();
//重置连续成功次数
manager.resetConsecutiveSuccessCount();
} }
/**
* 熔断器闭合状态
* 在闭合状态下,如果发生错误,并且错误次数达到阈值,则状态机切换到断开状态
*/
public class ClosedState extends AbstractBreakerState { public ClosedState(BreakerManager manager) {
super(manager); //重置失败计数器
manager.resetFailureCount();
} @Override
public void ActUponException() {
super.ActUponException(); //如果失败次数达到阈值,则切换到断开状态
if (manager.failureThresholdReached()) {
manager.moveToOpenState();
}
}
}
/**
* 熔断器断开状态
* 断开状态内部维护一个计数器,如果断开达到一定的时间,则自动切换到半断开状态,并且,在断开状态下,如果需要执行操作,则直接抛出异常。
*/
public class OpenState extends AbstractBreakerState { public OpenState(BreakerManager manager) {
super(manager); final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
timeoutHasBeenReached();
timer.cancel();
}
}, manager.timeout);
} @Override
public void protectedCodeIsAboutToBeCalled() {
super.protectedCodeIsAboutToBeCalled();
throw new RuntimeException("服务已熔断,请稍等重试!");
} /**
* 断开超过设定的阈值,自动切换到半断开状态
*/
private void timeoutHasBeenReached()
{
manager.moveToHalfOpenState();
}
}
/**
* 熔断器半断开状态
* 切换到半断开状态时,将连续成功调用计数重置为0,当执行成功的时候,自增该字段,当达到连读调用成功次数的阈值时,切换到闭合状态。
* 如果调用失败,立即切换到断开模式。
*/
public class HalfOpenState extends AbstractBreakerState { public HalfOpenState(BreakerManager manager) {
super(manager); //重置连续成功计数
manager.resetConsecutiveSuccessCount();
} @Override
public void ActUponException() {
super.ActUponException(); //只要有失败,立即切换到断开模式
manager.moveToOpenState();
} @Override
public void protectedCodeHasBeenCalled() {
super.protectedCodeHasBeenCalled(); //如果连续成功次数达到阈值,切换到闭合状态
if (manager.consecutiveSuccessThresholdReached()) {
manager.moveToClosedState();
}
}
}

状态管理器:

/**
* 熔断器管理类
*/
public class BreakerManager { public int failureCount; //失败次数
public int consecutiveSuccessCount; //连续成功次数
public int failureThreshold; //最大调用失败次数
public int consecutiveSuccessThreshold; //连续调用成功次数
public int timeout; private AbstractBreakerState state; //当前熔断器状态 public boolean isClosed() {
return state instanceof ClosedState;
} public boolean isOpen() {
return state instanceof OpenState;
} public boolean isHalfOpen() {
return state instanceof HalfOpenState;
} protected void moveToClosedState() {
state = new ClosedState(this);
} protected void moveToOpenState() {
state = new OpenState(this);
} protected void moveToHalfOpenState() {
state = new HalfOpenState(this);
} protected void increaseFailureCount() {
failureCount++;
} public void resetFailureCount() {
failureCount = 0;
} protected boolean failureThresholdReached() {
return failureCount >= failureThreshold;
} protected void increaseSuccessCount() {
consecutiveSuccessCount++;
} protected void resetConsecutiveSuccessCount() {
consecutiveSuccessCount = 0;
} protected boolean consecutiveSuccessThresholdReached() {
return consecutiveSuccessCount >= consecutiveSuccessThreshold;
} /**
* Close状态下最大失败次数,HalfOpen状态下使用的最大连续成功次数,以及Open状态下的超时时间
* 在初始状态下,熔断器切换到闭合状态
* @param failureThreshold
* @param consecutiveSuccessThreshold
* @param timeout
*/
public BreakerManager(int failureThreshold, int consecutiveSuccessThreshold, int timeout)
{
if (failureThreshold < 1 || consecutiveSuccessThreshold < 1) {
throw new RuntimeException("熔断器闭合状态的最大失败次数和半熔断状态的最大连续成功次数必须大于0!");
}
if (timeout < 1) {
throw new RuntimeException("熔断器断开状态超时时间必须大于0!");
}
this.failureThreshold = failureThreshold;
this.consecutiveSuccessThreshold = consecutiveSuccessThreshold;
this.timeout = timeout;
moveToClosedState();
} /**
* 该方法用于测试
* 通过AttempCall调用,传入期望执行的代理方法,该方法的执行受熔断器保护。这里使用了锁来处理并发问题
*/
public void attemptCall(boolean rs, int times) { for(int i=0; i<times; i++) { //需要加同步锁
state.protectedCodeIsAboutToBeCalled(); try {
//调用服务
if(!rs) {
throw new Exception();
} else {
System.out.println("第"+(i+1)+"服务调用成功!");
} } catch (Exception e) {
//需要加同步锁
System.out.println("第"+(i+1)+"服务调用超时!");
state.ActUponException();
} //需要加同步锁
state.protectedCodeHasBeenCalled();
}
} /**
* 手动切换到闭合状态
*/
public void close() {
//需要加同步锁
moveToClosedState();
} /**
* 手动切换到断开状态
*/
public void open() {
//需要加同步锁
moveToOpenState();
}
}

测试类:

public class Test {
public static void main(String[] args) { //定义熔断器,失败10次进入断开状态
//在半断开状态下,连续成功15次,进入闭合状态
//5秒后进入半断开状态
BreakerManager manager = new BreakerManager(10, 15, 5000);
showState(manager); //模拟失败10次调用
manager.attemptCall(false, 10);
System.out.println(manager.failureCount);
showState(manager); //这里如果再调用一次服务,正常会抛出“服务已熔断”的异常
//manager.attemptCall(true, 1); //等待熔断器超时,从Open转到HalfOpen
try {
System.out.println("等待熔断器超时(6s)。。。");
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
showState(manager); //模拟成功调用15次
manager.attemptCall(true, 10);
//这里如果出现一次调用服务失败,熔断器会马上进入熔断状体,接下来的调用会抛出“服务已熔断”的异常
//manager.attemptCall(false, 1);
manager.attemptCall(true, 5); System.out.println(manager.consecutiveSuccessCount);
System.out.println(manager.failureCount);
showState(manager); } public static void showState(BreakerManager manager) {
System.out.println("Breaker is Closed:" + manager.isClosed());
System.out.println("Breaker is Open:" + manager.isOpen());
System.out.println("Breaker is isHalfOpen:" + manager.isHalfOpen());
}
}

测试结果:

Breaker is Closed:true
Breaker is Open:false
Breaker is isHalfOpen:false
第1服务调用超时!
第2服务调用超时!
第3服务调用超时!
第4服务调用超时!
第5服务调用超时!
第6服务调用超时!
第7服务调用超时!
第8服务调用超时!
第9服务调用超时!
第10服务调用超时!
10
Breaker is Closed:false
Breaker is Open:true
Breaker is isHalfOpen:false
等待熔断器超时(6s)。。。
Breaker is Closed:false
Breaker is Open:false
Breaker is isHalfOpen:true
第1服务调用成功!
第2服务调用成功!
第3服务调用成功!
第4服务调用成功!
第5服务调用成功!
第6服务调用成功!
第7服务调用成功!
第8服务调用成功!
第9服务调用成功!
第10服务调用成功!
第1服务调用成功!
第2服务调用成功!
第3服务调用成功!
第4服务调用成功!
第5服务调用成功!
15
0
Breaker is Closed:true
Breaker is Open:false
Breaker is isHalfOpen:false

参考文章:

http://www.cnblogs.com/yangecnu/p/Introduce-Circuit-Breaker-Pattern.html

http://martinfowler.com/bliki/CircuitBreaker.html

http://msdn.microsoft.com/en-us/library/dn589784.aspx

https://yq.aliyun.com/articles/7443

http://www.tuicool.com/articles/AbiqEnn

最新文章

  1. ES6之const命令
  2. EditPlus 3.7 中文版已经发布
  3. cassandra的源代码的入口
  4. ubuntu14.04 安装配置JDK1.7
  5. JS中数据类型及原生对象简介
  6. 【微博SDK调用逻辑】微博SDK的调用逻辑,最好自己还是写一个例子,试一下!!!
  7. Windows 8.1 应用再出发 - 几种常用控件
  8. JavaScript实现MVVM之我就是想监测一个普通对象的变化
  9. 点分十进制IP校验、转换,掩码校验
  10. MSP下载方式
  11. C#关于ref与out的总结
  12. AspNet MVC4 教育-28:Asp.Net MVC4 Ajax技术部门四舍五入余速Demo
  13. 妙用transform
  14. mysql(4)—— 表连接查询与where后使用子查询的性能分析。
  15. Excel VBA 连接各种数据库(二) VBA连接Oracle数据库
  16. [转]Git &amp; Gitlab 使用指南
  17. [C# 面试总结]9个点如何画10条线
  18. JNI探秘-----你不知道的FileInputStream的秘密
  19. S4 对象系统
  20. 用仿ActionScript的语法来编写html5——第一篇,显示一张图片

热门文章

  1. Java中try-catch-finally的一点理解
  2. updating the chroot
  3. Away 3d 入门demo
  4. Nginx 和 IIS 实现动静分离【转载】
  5. 好题 线段树对数据的保存+离线的逆向插入 POJ 2887
  6. wpa_supplicant wpa_cli 的使用说明
  7. go mode
  8. struts2修改文件上传的大小
  9. 【转】dmidecode命令详解
  10. ALAssetsLibrary 照片相关 浅析