一、断路器介绍
断路器,在我们中学时的物理课中讲过,为了保护电路,防止短路、过载一般会有保险丝,现在有更高级的保护措施和设备,但目的始终保持一致。在我们的程序中也有类似的情况,用户请求就好比电路中的负载,虽然现在很多系统都采用分布式,但单机所能承受的负载是有限的,同时也不能完全保证程序100%不出现问题,所以程序中的“保险丝”就能有效的发挥作用了。
二、代码实现
话不多说,直接上代码实现
- CircuitBreaker ····· 断路器
- CircuitBreakerConfig ····· 断路器配置
- CircuitBreakerState ····· 断路器状态
- CircuitException ····· 断路器异常
- ProtectedAction ····· 执行接口
- TestBreaker ····· 测试
1.基本类信息
/**
* 配置信息类
*
* @author bingo
*/
public class CircuitBreakerConfig {
/* 关闭状态最大线程数 */
public int MAX_THREAD_CLOSED = 100;
/* 半开状态最大线程数 */
public int MAX_THREAD_HALFOPEN = 5;
/* 打开状态多少毫秒后,自动转为半关闭状态 */
public long OPEN_TIME = 2000;
/* 半开状态下,当连续成功了多少次以后,置为关闭 */
public int SUCCESS_COUNT_CLOSED = 5;
/* 关闭状态下,当连续失败了多少次以后,置为打开 */
public int FAILURE_COUNT_OPEN = 5;
}
public interface ProtectedAction {
/* 保护执行操作 */
Object execute() throws Exception;
boolean isBreakException(Exception e);
}
/**
* 断路器
*
* @author bingo
*/
public class CircuitBreaker {
// 断路器名称
private String name;
// 配置信息
private CircuitBreakerConfig config;
// 当前状态
private CircuitBreakerState state = CircuitBreakerState.CLOSED;
// 并发线程数
private int threadCount = 0;
// 成功次数,半开状态使用
private int success_halfOpen = 0;
// 失败次数,关闭状态使用
private int failure_closed = 0;
// 状态置为Open的时间点,打开状态使用
private long openTime;
// 构造器
public CircuitBreaker(CircuitBreakerConfig config, String name) {
super();
this.config = config;
this.name = name;
}
// 构造器
public CircuitBreaker(String name) {
this(new CircuitBreakerConfig(), name);
}
/* 保护并执行代码 */
public Object execute(ProtectedAction action) throws CircuitException,
Exception {
doPrepare();
try {
Object result = action.execute();
doSuccess();
return result;
} catch (Exception e) {
if (action.isBreakException(e)) {
doFailure();
}
throw e;
} finally {
doFinally();
}
}
private synchronized void doPrepare() throws CircuitException {
switch (this.state) {
case OPEN:
/*打开状态,未到期不执行,到期切换为半开状态*/
if (threadCount >= config.MAX_THREAD_HALFOPEN
|| System.currentTimeMillis() - openTime < config.OPEN_TIME) {
throw new CircuitException();
}
changeState(CircuitBreakerState.HALFOPEN);
break;
case CLOSED:
/*关闭状态,超出线程不执行*/
if (threadCount >= config.MAX_THREAD_CLOSED) {
throw new CircuitException();
}
break;
case HALFOPEN:
/*半开状态,超出线程不执行*/
if (threadCount >= config.MAX_THREAD_HALFOPEN) {
throw new CircuitException();
}
break;
}
threadCount++;
}
private synchronized void doSuccess() {
switch (this.state) {
case OPEN:
/*打开状态,重置失败次数*/
failure_closed = 0;
break;
case HALFOPEN:
/*半开状态,成功次数累计,达到阀值后置为关闭状态*/
success_halfOpen++;
if (success_halfOpen >= config.SUCCESS_COUNT_CLOSED) {
changeState(CircuitBreakerState.CLOSED);
}
break;
}
}
// 执行失败
private synchronized void doFailure() {
switch (this.state) {
case CLOSED:
// 关闭状态,失败次数累计,达到阀值后置为打开状态
failure_closed++;
if (failure_closed >= config.FAILURE_COUNT_OPEN) {
changeState(CircuitBreakerState.OPEN);
}
break;
case HALFOPEN:
// 半开状态,置为打开状态
changeState(CircuitBreakerState.OPEN);
break;
}
}
// 改变状态
private void changeState(CircuitBreakerState state) {
if (this.state == state) {
return;
}
// 成功 失败次数置为 0
this.success_halfOpen = 0;
this.failure_closed = 0;
if (state == CircuitBreakerState.OPEN) {
// 打开的时间点
this.openTime = System.currentTimeMillis();
if (this.state == CircuitBreakerState.CLOSED) {
System.out.println("CircuitBreaker[" + this.name + "] State Changed : " + this.state +
" --> " + state);
} else {
System.out.println("CircuitBreaker[" + this.name + "] State Changed : " + this.state +
" --> " + state);
}
} else {
this.openTime = 0;
if (state == CircuitBreakerState.CLOSED) {
System.out.println("CircuitBreaker[" + this.name + "] State Changed : " + this.state +
" --> " + state);
} else {
System.out.println("CircuitBreaker[" + this.name + "] State Changed : " + this.state +
" --> " + state);
}
}
this.state = state;
}
// 执行结束 并发线程数减 1
private synchronized void doFinally() {
this.threadCount--;
}
}
2.实现原理
借用一张原理图
断路器分为三种状态:
-
关闭状态:关闭状态可接收请求,同时失败后会计数器会+1,若连续失败次数超过最大值,则断路器状态切换为打开状态
-
打开状态:判断打开时间是否到期,未到期拒绝所有请求,到期则切换为半开状态,可接受请求
-
半开状态:可接收请求,若连续成功达到配置值,则切换为关闭状态,若失败,则切换为打开状态,且重置成功次数
执行流程:
初始为关闭状态,只限制最大线程数(也就是限流),执行成功重置失败次数。当连续失败达到最大值,则进入打开状态,此时拒绝所有请求,待断路器打开时间达到配置值,则进入半开状态,此时失败则返回打开状态,直到连续成功次数达到预定值,则断路器进入关闭状态。
总结
至此,断路器的简单实现已经完成了,本次程序设计采用状态模式,根据执行情况来切换断路器状态,从而给系统缓冲时间恢复。当然实际情况可能更复杂,分布式、微服务现在很常见,与此类似使用的中间件也有很多如:Hystrix、Sentinel等。因个人能力有限,若有错误之处请及时指出,本人将及时纠正,感谢。