熔断器模式:微软提出来的一种模式:Circuit Breaker Pattern
在现在的微服务应用中,服务间依赖性强,经常会出现一些故障,而一些故障会直接或者间接的拖垮其它的服务,造成服务器雪崩,系统就会死掉。我们需要解决的是,当某一个微服务发生蔓延当时候,不能发生故障蔓延,整个系统还能以其它某种方式正常运行。此时这里我们会涉及到几个微服务治理的概念:
服务熔断
当下游的服务因为某种原因导致服务不可用或响应过慢时,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回。当下游服务恢复后,上游服务会恢复调用。
服务容错
分布式架构中,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间等待,这样不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
服务降级
当我们的服务发生熔断的时候,那么就需要降级了,那么什么是降级?降级指的是A服务调用B服务,没有调用成功,发生熔断,那么A服务就不要死板的一直请求B服务,而是去服务上哪一个缓存先顶着,避免给我们的用户,响应一些错误的页面,这个就是服务降级。
先说下 Netflix Hystrix 这个断路器,这个是SpringCloud中最早支持的一种容错方案,由于SpringCloud摒弃了已经不再继续维护的 Hystrix 断路器,采用官方推荐的 Resilience4J 作为底层实现。
Resilience4J是我们Spring Cloud G版本 推荐的容错方案,它是一个轻量级的容错库。借鉴了Hystrix而设计,并且采用JDK8 这个函数式编程,也就是我们的lambda表达式,为什么说它是轻量级的呢?因为它的库只使用 Vavr (以前称为 Javaslang ),它没有任何其他外部库依赖项。相比之下, Netflix Hystrix 对Archaius 具有编译依赖性,这导致了更多的外部库依赖,例如 Guava 和 Apache Commons ,分析Hystrix的源码即可清楚。而如果使用Resilience4j,你无需引用全部依赖,可以根据自己需要的功能引用相关的模块即可。
Resilience4J 提供了一系列增强微服务的可用性功能:
可以选择使用定制化的配置有:
基于实际项目,我们在程序消费端引入依赖resilience4j-spring-boot2
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>${resilience4j.version}</version>
</dependency>
实现Resilience4j服务熔断有两种方式,一种就是Spring Aop式,另一种就是编程式,下面我们就来用这两种方法来实现。
在application.yml添加如下依赖 :
resilience4j.circuitbreaker:
backends:
# backendA 是断路器策略的命名
backendA:
# 表示断路器关闭状态下,环形缓冲区的大小
ringBufferSizeInClosedState: 5
# 表示断路器处于 HalfOpen 状态下,环形缓冲区的大小
ringBufferSizeInHalfOpenState: 3
# 表示断路器从 open 切换到 half closed 状态时,需要保持的时间
waitInterval: 5000
# 表示故障率阈值百分比,超过这个阈值,断路器就会打开
failureRateThreshold: 50
# 表示事件缓冲区大小
eventConsumerBufferSize: 10
# 表示开启健康检测
registerHealthIndicator: true
recordFailurePredicate: cn.com.scitc.RecordFailurePredicate
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
ignoreExceptions:
- org.springframework.web.client.HttpClientErrorException
配置完成后,接下来我们来定义一个名为 HelloServiceCircuitBreaker 的类,在这个类中,来定义服务请求方法:
@Service
@CircuitBreaker(name = "backendA")
public class HelloServiceCircuitBreaker {
@Autowired
RestTemplate restTemplate;
public String hello(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
}
}
这里通过 @CircuitBreaker 注解来启用断路器。最后在controller层中调用这个方法即可。
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(String name) {
return helloService.hello( name );
}
}
但是这种写法有一个问题,就是没法进行服务容错降级,如果希望进行服务容错降级,那么还是需要通过编程实现断路器功能。
通过编程实现断路器功能,就不再需要 application.yml 中的配置了,也不需要在类上添加 @CircuitBreaker(name = “backendA”) 注解,所有的相关配置都是在 Java 代码中完成。
public String hello2(String name) {
//创建一个 CircuitBreakerConfig 出来,然后配置一下故障率阈值,等待时间以及环形缓冲区大小等;
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.ringBufferSizeInHalfOpenState(20)
.ringBufferSizeInClosedState(20)
.build();
//根据第一步创建出来的 CircuitBreakerConfig ,再去创建一个 CircuitBreaker 对象;
io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("backendA", circuitBreakerConfig);
//通过 Try.ofSupplier 方法去发送一个请求,如果请求抛出异常,则在 recover 方法中进行服务降级处理,recover 可以写多个;
Try<String> supplier = Try.ofSupplier(io.github.resilience4j.circuitbreaker.CircuitBreaker
.decorateSupplier(circuitBreaker,
() -> restTemplate.getForObject("http://provider/hello?name={1}", String.class, name)))
.recover(Exception.class, "有异常,访问失败!");
return supplier.get();
}
最后,在 controller层 中调用这里的 hello2 方法去访问 provider 中的接口。接口调用失败后, consumer 中自动进行服务降级,最终返回字符串为 有异常,访问失败!
在我们的项目上线后,服务间调用就容易出现问题,比如网络的波动造成服务调用失败,在生产环境中,网络原因造成服务调用失败是不可以忽略的,那么我们一定要进行请求重试。
在服务请求方中的application.yml中添加如下配置:
resilience4j.retry:
retryAspectOrder: 399
backends:
retryBackendA:
maxRetryAttempts: 3
waitDuration: 600
eventConsumerBufferSize: 1
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
enableRandomizedWait: false
randomizedWaitFactor: 2
retryExceptionPredicate: cn.com.scitc.RecordFailurePredicate
retryExceptions:
- java.io.IOException
ignoreExceptions:
- cn.com.scitc.exception.IgnoredException
关于上面的配置解释如下:
RecordFailurePredicate的定义如下:
public class RecordFailurePredicate implements Predicate<Throwable> {
@Override
public boolean test(Throwable throwable) {
return true;
}
}
接下来我们还需要在 启动类中配置一个RestTemplate:
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run( ConsumerApplication.class, args );
}
@LoadBalanced
@Bean
RestTemplate restTemplate () {
return new RestTemplate();
}
}
我们还需要一个HelloService,在controller层来进行调用:
@Service
@Retry(name = "retryBackendA")
public class HelloService {
@Autowired
RestTemplate restTemplate;
public String hello(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
}
}
下面我们来讲一下 Resilience4J 中的限流工具 RateLimiter,我们同样需要在application.yml文件中配置:
resilience4j.ratelimiter:
limiters:
# backendA 在这里依然表示配置的名称,在 Java 代码中,我们将通过指定限流工具的名称来使用某一种限流策略
backendA:
# limitForPeriod 表示请求频次的阈值
limitForPeriod: 1
# limitRefreshPeriodInMillis 表示频次刷新的周期
limitRefreshPeriodInMillis: 5000
# timeoutInMillis 许可期限的等待时间,默认为5秒
timeoutInMillis: 5000
# subscribeForEvents 表示开启事件订阅
subscribeForEvents: true
# registerHealthIndicator 表示开启健康监控
registerHealthIndicator: true
# eventConsumerBufferSize 表示事件缓冲区大小
eventConsumerBufferSize: 100
配置完成后,创建一个 HelloServiceRateLimiter 类,添加注解后通过controller层调用,内容如下:
@Service
@RateLimiter(name = "backendA")
public class HelloServiceRateLimiter {
@Autowired
RestTemplate restTemplate;
public String hello(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
}
}
下面我们通过编程式来实现限流:
我们使用 RateLimiterRegistry 来统一管理 RateLimiter ,也可以通过 RateLimiter.of 方法来直接创建一个 RateLimiter。创建好了,就可以直接使用了,代码如下:
public void hello2(String name) {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofMillis(5000))
.limitForPeriod(1)
.timeoutDuration(Duration.ofMillis(6000))
.build();
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);
RateLimiter rateLimiter = RateLimiter.of("backendB", config);
Supplier<String> supplier = RateLimiter.decorateSupplier(rateLimiter, () ->
restTemplate.getForObject("http://provider/hello?name={1}", String.class, name)
);
for (int i = 0; i < 5; i++) {
Try<String> aTry = Try.ofSupplier(supplier);
System.out.println(aTry.get());
}
}
在限流中,我们可以获取所有允许和拒绝执行的事件信息,获取方式如下:
rateLimiter.getEventPublisher()
.onSuccess(event -> {
System.out.println(new Date()+">>>"+event.getEventType()+">>>"+event.getCreationTime());
})
.onFailure(event -> {
System.out.println(new Date()+">>>"+event.getEventType()+">>>"+event.getCreationTime());
});
本文向读者介绍了 Resilience4j 的一些基本功能,这些基本功能涵盖了请求熔断、限流、以及重试等功能,介绍了 Resilience4j 的一些基本用法。
引用:https://github.com/resilience4j/resilience4j#circuitbreaker
https://blog.csdn.net/qwe86314/article/details/98984374