Resilience4j提供了熔断、限流、重试等一系列有助于服务稳定性的功能,Springboot项目可以非常简单的进行集成。
<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot3</artifactId><version>2.3.0</version> </dependency>
Circuitbreaker
在你的服务访问其他某服务返回结果异常, 比如5xx, timeout,可以采用Circuitbreaker(断路器)阻止后续请求继续访问异常服务,同时设置failbackMethod进行降级处理。
断路器的实现模型是一个有限状态机,分为close,open, half-open三个状态。

Close: 表示断路器并没有阻断了业务逻辑(方法的执行)。
Open: 表示断路器已打开阻断了业务逻辑向下游的执行,可以指定执行failbackMethod进行降级处理。
Half_open: 表示断路器为半开半闭状态,会尝试放行个别请求向下游执行,如果执行成功则关闭断路器(Close),如果执行失败则打开断路器(Open)。
circuitbreaker的常用配置参数:
minimumNumberOfCalls: 2 #至少2次请求后进行判断 failureRateThreshold: 50f #一半请求失败触发开启断路器 slidingWindowSize: 6 #每次判断的滑动窗口大小是6 slidingWindowType: COUNT_BASED #计算方式count_based,另一个是TIME_BASED automaticTransitionFromOpenToHalfOpenEnabled: true #自动从open转成half-open waitDurationInOpenState: 5000 #断路器在open状态的持续时间5秒 maxWaitDurationInHalfOpenState: 0 #在半开状态的等待时间0秒
example:配置一个name="sayHelloWorld"的断路器。
resilience4j:circuitbreaker:instances:sayHelloWorld:minimumNumberOfCalls: 2failureRateThreshold: 50slidingWindowSize: 6slidingWindowType: COUNT_BASEDautomaticTransitionFromOpenToHalfOpenEnabled: truewaitDurationInOpenState: 5smaxWaitDurationInHalfOpenState: 0
通过注解的方式在类或者方法上使该断路器
@CircuitBreaker(name = "sayHelloWorld") public String sayHiNginx() {return webClientBuilder.baseUrl("http://127.0.0.1:8083").build().get().retrieve().bodyToMono(String.class).block(); //采用WebClient的响应式模式发http请求,不用block()的情况下这种代码不work }
这样当2次访问127.0.0.1:8083,至少1次以上不通会触发断路器。
{"status": 500,"message": "internal server error","response": "CircuitBreaker 'sayHelloWorld' is OPEN and does not permit further calls" }
响应式编程遇到的问题
第一版写完代码进行测试的时候发现限流器根本没起作用,以为是配置的不对,后来发现代码中有1处操作导致限流器没有生效。
sayHiNginx方法想借助spring框架响应式触发http请求的结果并返回给API的调用方,但是CircuitBreaker并没有统计到http请求失败。
PS:我的测试环境里127.0.0.1:8083套接字并不存在。
@CircuitBreaker(name = "sayHelloWorld") public Mono<String> sayHiNginx() {return webClientBuilder.baseUrl("http://127.0.0.1:8083").build().get().retrieve().bodyToMono(String.class); //注意没有block() }
@GetMapping("/smart/resilience/circuitbreaker")
public Mono<String> testCircuitBreaker() {
return resilienceService.sayHiNginx();
}
原因是项目里之前写了一个HttpRequestLogPrinterFilter类进行HttpServlet request和response的拷贝。问题出在ContentCachingResponseWrapper 与响应式类型不兼容,当 Controller 返回 Mono<String> 时,响应体是异步写入的,而 ContentCachingResponseWrapper 在 finally 中调用 copyBodyToResponse() 时,响应体可能尚未写入,导致复制为空。
public class HttpRequestLogPrinterFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);try {filterChain.doFilter(requestWrapper, responseWrapper);} finally {responseWrapper.copyBodyToResponse();}} }
把这种异步的响应式触发改成了同步等待--加block()--http返回结果之后解决问题,但WebClient作为为响应式编程设计的httpclient,线程比较少,block()操作会占用一个珍贵的线程,这样做显然不是一个好方案。
好在resilience4j也支持了响应式编程,添加依赖:
<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-reactor</artifactId><version>2.3.0</version> </dependency>
初始化一个circuitBreaker bean
@Configuration public class ResilienceConfig {@Bean(name="circuitBreaker")public CircuitBreaker initCircuitBreaker(CircuitBreakerRegistry cbRegistry) {return cbRegistry.circuitBreaker("sayHelloWorld");} }
修改sayHiNginx方法,在末尾用transform方法把响应流转换成另一个响应流并加上CircuitBreakerOperator方法。CircuitBreakerOperator方法天然的支持reactor模式,主要实现一个UnaryOperator的apply方法应用熔断器。
@CircuitBreaker(name = "sayHelloWorld")
public Mono<String> sayHiNginx() {return webClientBuilder.baseUrl("http://127.0.0.1:8083").build().get().retrieve().bodyToMono(String.class).transform(CircuitBreakerOperator.of(circuitBreaker)); }
这样在control层直接返回Mono对象时,spring就能成功触发熔断器的执行了。
@GetMapping("/smart/resilience/circuitbreaker")
public Mono<String> testCircuitBreaker() {return resilienceService.sayHiNginx();
}