简介
本文介绍SpringCloud的hystrix的断路器的原理。
本内容也是Java后端面试常见的问题。
原理
断路器是如何决策熔断和记录信息的呢?
看断路器HystrixCircuitBreaker的定义:
public interface HystrixCircuitBreaker {
public static class Factory {...}
static class HystrixCircuitBreakerlmpl implements HystrixCircuitBreaker {… }
static class NoOpCircuitBreaker implements HystrixCircuitBreaker {… }
public boolean allowRequest();
public boolean isOpen();
void markSuccess() ;
}
可以看到它的接口定义并不复杂,主要定义了三个断路器的抽象方法。
- allowRequest():每个Hystrix命令的请求都通过它判断是否被执行。
- isOpen():返回当前断路器是否打开。
- markSuccess():用来闭合断路器。
另外还有三个静态类。
- 静态类Factory中维护了一个Hystrix命令与HystrixCircuitBreaker的关系集合:ConcurrentHashMap<String,HystrixCircuitBreaker>cpircuitBreakersByCommand,其中String类型的key通过HystrixCommandKey定义,每一个Hystrix命令需要有一个key来标识,同时一个Hystrix命令也会在该集合中找到它对应的断路器HystrixCircuitBreaker实例。
- 静态类NoOpCircuitBreaker定义了一个什么都不做的断路器实现,它允许所有请求,并且断路器状态始终闭合。
- 静态类HystrixCircuitBreakerlmpl是断路器接口HystrixCircuitBreaker的实现类,在该类中定义了断路器的4个核心对象。
- HystrixCommandPropertiesproperties:断路器对应HystrixCommand实例的属性对象,它的详细内容我们将在后续章节做具体的介绍。
- HystrixCommandMetricsmetrics:用来让HystrixCommand记录各类度量指标的对象。
- AtomicBooleancircuitOpen:断路器是否打开的标志,默认为false。
- AtomicLongcircuitOpenedOrLastTestedTime:断路器打开或是上一次测试的时间戳。
HystrixCircuitBreakerlmpl对HystrixCircuitBreaker接口的各个方法实现如下所示。
isOpen()
判断断路器的打开/关闭状态。
- 如果断路器打开标识为true,则直接返回true,表示断路器处于打开状态。否则,就从度量指标对象metrics中获取HealthCounts统计对象做进一步判断(该对象记录了一个滚动时间窗内的请求信息快照,默认时间窗为10秒)。如果它的请求总数(QPS)在预设的阈值范围内就返回false,表示断路器处于未打开状态。该阈值的配置参数为circuitBreakerRequestVolumeThreshold,默认值为20。
- 如果错误百分比在阈值范围内就返回false,表示断路器处于未打开状态。该阈值的配置参数为circuitBreakerErrorThresholdPercentage,默认值为50。
- 如果上面的两个条件都不满足,则将断路器设置为打开状态(熔断/短路)。同时,如果是从关闭状态切换到打开状态的话,就将当前时间记录到上面提到的circuitOpenedOrLastTestedTime对象中。
isOpen()源码
public boolean isOpen() {
if (circuitOpen.get()) {
return true;
HealthCounts health = metrics.getHealthCounts ();
if (health.getTotalRequests() {}
properties.circuitBreakerRequestVolumeThreshold().get()) {
return false;
}
if (health.getErrorPercentage() {
properties.circuitBreakerErrorThresholdPercentage().get()) {
return false;
} else {
if (circuitOpen.compareAndSet(false, true)) {
circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
return true;
} else {
return true;
}
}
}
allowRequest()
作用:判断请求是否被允许。
先根据配置对象properties中的断路器判断强制打开或关闭属性是否被设置。如果强制打开,就直接返回false,拒绝请求。如果强制关闭,它会允许所有请求,但是同时也会调用
isOpen()来执行断路器的计算逻辑,用来模拟断路器打开/关闭的行为。在默认情况下,断路器并不会进入这两个强制打开或关闭的分支中去,而是通过!isOpen()||allowSingleTest()来判断是否允许请求访问。!isOpen()之前已经介绍过,用来判断和计算当前断路器是否打开,如果是关闭状态就允许请求。
allowRequest()源码
@Override
public boolean allowRequest() {
if (properties.circuitBreakerForceOpen().get()) {
return false;
}
if (properties.circuitBreakerForceClosedO .get()) {
isOpen ();
return true;
}
return !isOpen( ) || allowSingleTest();
}
那么allowSingleTest()是用来做什么的呢?
public boolean allowSingleTest () {
long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get();
if (circuitOpen.get() && System.currentTimeMillis())
timeCircuitOpenedOrWasLastTested + properties.circuitBreakerSleepWindowInMilliseconds{).get ()) {
if (circuitOpenedOrLastTestedTime.compareAndSet(
timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) {
return true;
}
}
return false;
}
从allowSingleTest()的实现中我们可以看到,这里使用了在isOpen()函数中当断路器从闭合到打开时候所记录的时间戳。当断路器在打开状态的时候,这里会判断断开时的时间戳+配置中的circuitBreakerSleepWindowInMilliseconds时间是否小于当前时间,是的话,就将当前时间更新到记录断路器打开的时间对象circuitOpenedOrLastTestedTime中,并且允许此次请求。简单地说,通过circuitBreakerSleepWindowInMilliseconds属性设置了一个断路器打开之后的休眠时间(默认为5秒),在该休眠时间到达之后,将再次允许请求尝试访问,此时断路器处于“半开”状态,若此时请求继续失败,断路器又进入打开状态,并继续等待下一个休眠窗口过去之后再次尝试;若请求成功,则将断路器重新置于关闭状态。所以通过allowSingleTest()与isOpen()方法的配合,实现了断路器打开和关闭状态的切换。
markSuccess()
该函数在“半开路”状态时使用。
若 Hystrix命令调用成功,通过调用它将打开的断路器关闭,并重置度量指标对象。
public void markSuccess() {
if (circuitOpen.get()) {
if (circuitOpen.compareAndSet(true, false)) {
metrics.resetStream();
}
}
}
其他网址
《SpringCloud微服务实战》
=> 第5章 服务容错保护:Spring Cloud Hystrix
=> 原理分析
=> 断路器原理
《深入理解Spring Cloud与微服务构建》
=> 8.4 Hystrix的工作机制