Spring Cloud怎么集成resilience4j提供的熔断器

一、背景

在微服务架构中,服务的稳定性和弹性至关重要。当某个服务出现故障或延迟时,服务熔断可以防止故障的扩散,保护系统的可用性和稳定性。之前项目中使用的Hystrix作为熔断器,但是Hystrix官方宣布不再进行更新,建议使用resilience4j作为熔断器。

 既然官方都推荐resilience4j,那么我们就去探索一下,resilience4j怎么集成到我们的项目中,以及能否达到预期的熔断效果。

二、什么是熔断器 ?

在集成resilience4j之前我们先回顾一下,什么是熔断器?

"熔断器"本身是一种开关装置,当某个服务单元发生故障后,通过熔断器的故障监控(类似电路中保险丝熔断),停止调用故障的服务。向调用方返回一个兜底的、符合预期的、可处理的备选响应,而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,甚至雪崩。

例如,某视频软件的用户服务发生故障,拿不到用户名称,立即对用户服务进行熔断并返回兜底的XX用户,在页面用户名字处统一显示XX用户。

三、熔断器 Circuit Breaker的工作原理

CircuitBreaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。下面是resilience4j官网中的介绍:

大概的意思是,Circuit Breaker是通过状态机实现的,正常的状态有关闭、打开和半开,还有两种特殊的状态禁止和强制打开。并且它使用滑动窗口来存储和聚合调用的结果,可以选择时间滑动窗口(常用)或者次数滑动窗口,结合配置进行不同状态之间的扭转,控制熔断器的关闭或打开。

从上面的状态扭转图中可以看出,当某个服务出现故障时,CircuitBreaker会将当前CLOSED状态切换成OPEN状态,也就是保险丝断了或者跳闸了。调闸后,服务不能一直断开,所以CircuitBreaker隔一段时间会放行几次请求查看服务是否恢复,这时候会切换到HALF_OPEN状态。如果服务恢复正常,就切换到CLOSED状态继续使用,如果还是异常状态,就切换会OPEN状态。

四、Spring Cloud中怎么集成resilience4j?

熔断器一般都是配在服务的调用方或者说上游服务,按照下面的步骤进行集成。

1. 在pom文件中引入circuitbreaker相关的包
<!--resilience4j-circuitbreaker-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 在application.yml文件中引入circuitbreaker的配置
#  Resilience4j CircuitBreaker 按照时间:TIME_BASED 的例子
#  10s时间窗口中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
#  等待20秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
#  如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。
resilience4j.circuitbreaker:
  configs:
    default:
      registerHealthIndicator: true #注册一个健康指示器,以便可以通过 Actuator 的健康端点监控熔断器的状态
      failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
      slidingWindowType: TIME_BASED # 滑动窗口的类型
      slidingWindowSize: 10 #滑动窗口的大小,配置TIME_BASED表示10秒,配置COUNT_BASED表示10个请求
      minimumNumberOfCalls: 5 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
      automaticTransitionFromOpenToHalfOpenEnabled: true #是否启用自动从开启状态过渡到半开状态,默认值为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常
      waitDurationInOpenState: 20s #从OPEN到HALF_OPEN状态需要等待的时间
      permittedNumberOfCallsInHalfOpenState: 3 #半开状态允许的最大请求数,默认值为10。在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果这些请求调用的失败率等于或高于50%,CircuitBreaker将重新进入开启状态。
      eventConsumerBufferSize: 10 #事件消费者的缓冲区大小,表示事件消费者最多可以缓存 10 个事件。
      recordExceptions: #记录哪些异常为失败
        - org.springframework.web.client.HttpServerErrorException
        - java.io.IOException
        - feign.FeignException
3. 在服务调用方调用下游服务时通过注解引入熔断器

写个简单的例子,方便后面看效果。

@RestController
public class OrderController {

    private static final Logger log = LoggerFactory.getLogger(OrderController.class);

    @Autowired
    private PayService payService;
    @GetMapping("/order/{id}")
    @CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")//配置熔断器
    public String order(@PathVariable("id") Integer id){
        log.info("Order id: {}", id);
        log.info("Request Pay For Order id: {}", id);
        //通过open feign远程调用支付服务
        return payService.payOrder(id);
    }

    //fallback就是服务降级后的兜底处理方法
    public String fallback(Integer id,Throwable t) {
        log.info("Pay Service invoke failed for order ID: {}", id);
        log.error("Error Message : {}", t.getMessage());
        return "Pay Service Was Busy Now. Please try again later!";
    }
}
4. 在被调用服务中增加一个接口
@RestController
public class PayController {

    private static final Logger log = LoggerFactory.getLogger(PayController.class);

    @GetMapping("/pay/{id}")
    public String pay(@PathVariable("id") Integer id){
        log.info("Pay For Order id: {}", id);
        if (id == 1) { // 模拟服务异常
            throw new RuntimeException("Pay Service Exception");
        }
        return "Pay Success";
    }
}
5. 进行测试

我们的测试场景是,在Order服务中去调用下游的Pay服务,在10s时间窗口内如果有50%的失败率,就会触发熔断器打开OPEN。熔断器打开后,会拒绝所有的请求,并返回兜底的数据。在熔断器打开20s后,最多会允许3次调用,测试服务是否恢复,同时切换至HALF_OPEN。如果这3次调用的失败率或慢调用率等于或高于50%,会切换回OPEN状态,反之则熔断器关闭CLOSED,允许调用。

1)访问正常的支付服务

2)模拟访问异常的支付服务 

由于支付服务异常,会返回兜底的数据

3)在10s时间窗口内模拟多次异常支付服务的调用,触发熔断器打开

多次请求后,日志中出现了下面的错误信息。

从上面的日志可以看出,前面5次请求调用都失败了,从6次开始熔断器开始工作了,没有继续调用下游的支付服务了,而是直接走降级方法返回了兜底的数据。

4)熔断器触发后20s允许远程调用检测服务是否恢复健康

在熔断器触发后一直请求,在20s后会允许远程调用。我们在浏览器中一直发起请求:

从上面的日志可以看出,在熔断20s后,切换至HALF_OPEN状态,允许请求通过以检测服务是否恢复健康。

5)切换至半开状态后,若远程服务任然不可用会怎么样?

我们可用看到,处于半开状态后,放行了3次请求测试(上面的配置文件中配置最多允许3次)。

这3次请求都没有成功,然后熔断器又重新打开处于OPEN状态,远程请求被熔断,走兜底的方案。

6)切换至半开状态后,若远程服务恢复正常会怎么样?

熔断器处于半开状态的时候,我们请求http://localhost:8082/order/12 模拟服务恢复正常。

这时候服务开始恢复正常调用,熔断器切换至CLOSED状态。

五、总结

通过上面订单、支付的一个简单调用测试,我们验证了熔断器的工作流程和预期一致。通过上述实现,当下游服务出现故障时,熔断器能够快递熔断掉下游服务的调用,并返回降级后的结果或是友好的异常提升。避免了长时间的等待和大量失败的调用,确保系统的稳定性和响应能力。这种模式在微服务框架中非常常见并且有效。

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值