在断路器对象中封装受保护的方法调用,该对象监控调用和断路情况,
调用失败次数触发阈值后,后续
调用直接由断路器返回错误,不再执行实际调用
。默认情况下,抛出的任何异常都计算率,可以进行配置。
CircuitBreaker
有三种状态:
Closed(
闭合
)
,
Open(
打开
)
,
HalfOpen(
半开
)
。
Closed
状态下它是闭合的,如果下游服务调用失败超过一定数量,它将变为Open
状态。当在
Open
状态下经过一段后,进入HalfOpen
状态。如果在
HalfOpen
状态下调用的失败超过阀值,则返回到
Open
状态,否则切换到Closed状态。
1. Maven导包
<!-- 熔断:resilience4j -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
2. 配置文件
Resilience 4j
是没有必须的配置项的
###Resilience 4j
##打开Resilience4j报告熔断器和限速器监控指标的Actuator端点,默认是禁用的
management.health.circuitbreakers.enabled=true
management.health.ratelimiters.enabled=true
### 断路器的配置
## 统计窗口,基于时间则创建对应长度的一个数组,如:10,则通过一个长度为10的数组记录每一秒的请求
数和失败数,然后循环写入记录的数据,基于数量统计时原理类似
# 配置是基于时间段统计,还是基于请求数统计,默认COUNT_BASED
resilience4j.circuitbreaker.configs.default.slidingWindowType=COUNT_BASED
# 如果是基于请求数统计,则表示请求的数量,如果是基于时间段统计,表示统计的秒数,默认100
resilience4j.circuitbreaker.configs.default.slidingWindowSize=3
##当慢调用比率超过阀值,则断路器打开
# 慢调用比率阀值,默认100%
resilience4j.circuitbreaker.configs.default.slowCallRateThreshold=100
# 响应时间超过以下值即视为慢调用,默认60000毫秒
resilience4j.circuitbreaker.configs.default.slowCallDurationThreshold=20000
## 状态维护
#故障率阀值,默认50;也就是统计范围内的请求失败率超过这个阀值,断路器进入Open状态
resilience4j.circuitbreaker.configs.default.failureRateThreshold=50
#在计算故障率之前所需的最小调用次数,不达到这个值,不计算故障率。默认100
#注意:如果统计窗口配置为10,此处配置为1,则第一次请求失败时故障率就会统计,即100%,会导致断路
器打开
resilience4j.circuitbreaker.configs.default.minimum-number-of-calls=1
#Open状态持续时间,默认60000毫秒;也就是断路器断开这个毫秒数后,进入HalfOpen状态,可以尝试调
用
resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=10000000
#HalfOpen状态时允许的呼叫数,默认10;也就是在HalfOpen状态下,可以发送这么多个请求,如果失败率
还是
#高于故障率阀值,则返回到Open状态,否则切换到Closed状态
resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState
=10
## 注册Health信息
resilience4j.circuitbreaker.configs.default.registerHealthIndicator=true
###熔断器实例配置,也就是配置单个实例
#backendA为实例名称,其他属性也可以配置,此处只是个例子
resilience4j.circuitbreaker.instances.backendA.registerHealthIndicator=true
3. 创建断路器
3.1 通过API的方式使用断路器
@Autowired //自动注入CircuitBreakerFactory
private CircuitBreakerFactory circuitBreakerFactory;
public Sample get(long id){
//通过circuitBreakerFactory 创建Resilience4J CircuitBreaker实例
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("get");
//通过run方法传入需要执行的方法和对应的降级方法,CircuitBreaker会捕获第一个参数的get方法抛出异常,然后降级处理,并且统计故障率
return circuitBreaker.run(() -> getRequest(id), throwable ->reliable(id,throwable));
}
/**
* 发送请求的方法
*/
private Sample getRequest(long id) {
//通过RestTemplate发送请求,状态码为4**、5**的响应抛出异常
}
/**
* 断路器的回调方法,断路器进行服务降级时调用。参数和返回值要和服务方法一致,最后需要加一 * 个Throwable参数
*/
public Sample reliable(long id, Throwable e) {
logger.info("服务降级..............Throwable..............");
//断路器打开抛出的异常
if (throwable instanceof CallNotPermittedException) {
CallNotPermittedException exception = (CallNotPermittedException) throwable;}
//RestTemplate的请求方法中收到了状态码为4**、5**的响应抛出的异常
if (throwable instanceof HttpStatusCodeException) {
HttpStatusCodeException exception = (HttpStatusCodeException) throwable;
}
return new Sample(id, "服务降级,原因:" + e.getMessage());
}
3.2 通过注解方式使用熔断器
@Service
public class SampleService implements ISampleService {
private static final Logger logger = LoggerFactory.getLogger(SampleService.class);
@Autowired
private RestTemplate restTemplate;
@CircuitBreaker(name = "get", fallbackMethod = "reliable")
public Sample get(long id) {
}
/* 断路器的回调方法,断路器进行服务降级时调用。参数和返回值要和服务方法一致,需要加一个CallNotPermittedException参数,这个只针对熔断进行降级。返回4*、5*响应码的异常由
Retry重试后降级。这个异常如果需要被其它处理器感知到,则可以继续抛出 */
public Sample reliable(long id, CallNotPermittedException e) {
logger.info("服务降级..............Throwable..............");
logger.error("", e);
return new Sample(id, "服务降级,原因:" + e.getMessage());
}
}
重点注意:
1. 如果要在get()方法上加 @Transactional 注解开启get方法的事务管理的话 ,需要将get方法中的调用下游服务器的代码,拆出来并在其声明前加熔断器的注解。
原因:注解事务和熔断器是通过JDK的动态代理实现的,熔断器的代理要在事务代理之前,会导致事务中的异常也会触发熔断器的统计,而熔断器是用于检测应用调用的下游服务所抛出的异常的,所以不合理;
2. 在get()方法中不能直接调用熔断器方法,因为调用时默认是this.调用,非代理对象,与实际目的不符合,也就是熔断器注解被跳过,没有作用(没用调用代理对象,反而调用了被代理对象)。
解决办法:JDK的动态代理的代理对象,会存储在AopContext中,但是从中取代理对象需要修改Spring AOP配置
@EnableAspectJAutoProxy(exposeProxy = true)
@Override
@Transactional
public Orders save(Orders orders){
Orders orders1 = orderDao.save(orders);
OrderService orderService = (OrderService) AopContext.currentProxy();
int num = orderService.discountStocks(orders);
logger.warn("扣减结果:{}",num);
if(num==0){
throw new StockShortException("库存不足");
}
return orders1;
}
@Override
@CircuitBreaker(name = "save",fallbackMethod = "fallbackMethod")
public int discountStocks(Orders orders){
logger.warn("orders:{}", orders);
RequestEntity<Products> requestEntity = RequestEntity.put("http://product-service/api/v1/products/" + orders.getProductId())
.contentType(MediaType.APPLICATION_JSON)
.body(new Products(orders.getNum()));
try {
logger.warn("try");
return restTemplate.exchange(requestEntity, Integer.class).getBody();
} catch (HttpServerErrorException ex) {
throw new ProductServerException("下游服务异常", ex);
}
}
public int fallbackMethod(Orders orders, CallNotPermittedException ex){
logger.warn("服务降级:{}", orders.getProductId());
throw new ProductServerException("断路器处于打开状态", ex);
}