resilience4j

949 阅读10分钟

什么是resilience4j

Resilience4j 是一个用于帮助开发者编写弹性和容错代码的轻量级 Java 库。它提供了多种 resilience 模式的实现,帮助应对分布式系统中的故障和延迟问题。Resilience4j 主要包括以下几个核心组件和特性:

  1. Circuit Breaker(断路器) :当后端服务出现故障或延迟时,断路器可以阻止对该服务的进一步调用,避免雪崩效应,并在一段时间后允许部分请求通过,以检测服务的恢复情况。
  2. Rate Limiter(限流器) :控制对特定服务或资源的访问速率,以防止过载,提高系统的稳定性。
  3. Retry(重试) :在服务调用失败时,根据配置自动进行重试,以增加调用成功的概率。
  4. Bulkhead(隔舱) :将不同的任务隔离在不同的线程池中,以防止某个任务的失败影响到其他任务的执行。
  5. Fallback(降级) :定义备用的响应或处理逻辑,当主要逻辑出现问题时进行回退,保证系统的基本功能可用性。

Resilience4j 相比于其他类似的库,如 Netflix 的 Hystrix,它更加轻量级且对函数式编程友好,与 Java 8 的 Lambda 表达式和函数式接口紧密集成,使得在编写弹性代码时更加灵活和简洁

1.Circuit Breaker(断路器):

断路器(Circuit Breaker)是一种软件设计模式,用于增强分布式系统的稳定性和容错能力。它主要用于处理服务之间的调用,在面对故障或延迟时,通过快速失败来避免对不可用的服务持续的资源浪费,同时在服务恢复正常后再次尝试调用。

工作原理:

  1. 状态转换

    • 关闭状态(Closed) :在正常情况下,断路器处于关闭状态,允许请求通过,并监控服务调用的情况。如果请求成功,断路器保持关闭状态;如果失败达到一定阈值(比如连续多次失败),则断路器转入开启状态。
    • 开启状态(Open) :一旦断路器进入开启状态,将阻止所有请求通过,并且在一段时间内不会尝试调用服务,这段时间称为断路器的恢复期(Reset Time)。请求到达断路器时将立即失败,并且断路器计时以监测服务是否恢复正常。
    • 半开启状态(Half-Open) :在断路器的恢复期结束后,会进入半开启状态,允许一个请求通过以测试服务是否已经恢复。如果这个请求成功,则断路器转回关闭状态;如果失败,则断路器重新进入开启状态。
  2. 监控和阈值

    • 断路器会监控请求的成功率和失败率,以及在开启状态下请求的数量,通过配置的阈值来决定何时打开断路器。
  3. 优点

    • 快速失败:在服务不可用时,避免请求长时间等待或超时,快速返回错误,提高系统的整体响应速度。
    • 资源保护:通过停止对故障服务的请求,避免了系统资源的浪费。

实现

导入pom依赖:

<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>

配置yml文件

resilience4j:
  timelimiter:
    configs:
      default:
        # 神坑的位置,timelimiter 默认限制远程 1s,超过 1s 就超时异常,配置了降级,就直接走降级逻辑了
        timeout-duration: 10s
  circuitbreaker:
    configs:
      default:
        #设置 50% 的失败率,超过失败请求百分比 CircuitBreaker 变为 OPEN 状态。
        failure-rate-threshold: 50
        # 滑动窗口类型
        sliding-window-type: TiME_BASED
        # 慢调用时间阈值,高于这个阈值的视为慢调用并增加慢调用比例。
        slow-call-duration-threshold:
          seconds: 2
        #慢调用百分比峰值,断路器把调用时间大于 slow-call-duration-threshold,视为慢调用,当慢调用比例高于这个的时候进入到 OPEN。
        slow-call-rate-threshold: 30
        # 滑动窗口的大小, 配置 COUNT_BASED 表示 2 个请求,配置 TIME_BASED 表示 2 秒。
        sliding-window-size: 2
        # 断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果 minimumNumberOfCalls 为 10, 则必须最少记录 10 个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
        minimum-number-of-calls: 2
        automatic-transition-from-open-to-half-open-enabled: true  # 是否启用自动从 OPEN ---> HALF_OPEN, 默认值就为 TRUE
        # 从 OPEN 到 HALF_OPEN状态需要等待的时间
        wait-duration-in-open-state:
          seconds: 10
        # 半开状态允许的最大请求值为
        permitted-number-of-calls-in-half-open-state: 2
        # 记录的异常类型
        record-exceptions:
          - java.lang.Exception
    # 以上的配置适用于那个微服务
    instances:
      cloud-payment-service: #需要断路的服务名称
        base-config: default

然后就可以在controller层中添加对应的接口,在接口中添加@CircuitBreaker(name = "", fallbackMethod = "")第一个参数name表示你需要断路的服务名是哪个,fallbackMethod兜底策略将会执行自定义的兜底方法(出现异常,服务不正常返回兜底方法,从而更新界面)

注意: 配置了全局异常处理的话记得关掉或者注释掉,因为异常处理器会比断路器更先捕捉到异常,直接返回对应的异常处理,从而没有调用到断路器的降级方法

可以用线程休眠或抛出异常的方法去尝试看看会返回什么结果.

除了异常,计数和时间也是会触发降级处理机制:

  • 在yml文件中配置了慢调用阈值,当调用时用时超过阈值被视为慢调用,而慢调用超过一定数量,服务器会出现断路现象

  • yml文件中也配置了单个请求的最长等待时间,当规定时间内没有响应,就会直接进行降级.

2.Bulkhead(隔舱)

隔舱(Bulkhead)是一种模式,用于将系统的不同部分或任务隔离开来,以确保一个部分的故障或高负载不会影响整个系统的稳定性和可用性。这个概念源自于船舶的隔舱结构,通过分隔舱室来防止船只在发生破损或者泄漏时沉没。

工作原理:

  1. 线程隔离

    • 在计算机系统中,隔舱通常通过线程池来实现。每个隔舱都有自己的线程池,处理特定类型的任务或服务请求。
    • 如果某个隔舱中的任务或服务发生故障或者超时,只会影响该隔舱内的任务,不会波及到其他隔舱或系统其他部分。
  2. 优点

    • 防止故障扩散:隔舱可以限制某个部分的问题,避免其影响到整个系统,提高系统的健壮性和可靠性。
    • 资源管理:通过为每个隔舱分配独立的资源(如线程池),可以更有效地管理系统资源,避免资源争用或者耗尽。
    • 提高可用性:即使某个隔舱出现问题,其他隔舱仍然可以继续运行,保持系统的部分功能可用。
  3. 应用场景

    • 在微服务架构中,可以为每个微服务分配独立的隔舱,以确保一个微服务的问题不会影响到其他微服务。
    • 在异步处理或并发任务中,通过隔舱可以控制并发度,避免某些任务的延迟或故障影响整体性能。

总之,隔舱模式是提高系统弹性和容错能力的重要手段,尤其在面对复杂的分布式系统或者高并发环境时,能有效地保护系统不受单点故障的影响。

Resilience4j提供了两种隔离的实现方式,可以限制并发执行的数量。

  • SemaphoreBulkhead使用了信号量
  • FixedThreadPoolBulkhead使用了有界队列和固定大小线程池

添加依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
</dependency>

yml:

####resilience4j bulkhead 的例子
resilience4j:
  bulkhead:
    configs:
      default:
        maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
        maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
    instances:
      cloud-payment-service:
        baseConfig: default
  timelimiter:
    configs:
      default:
        timeout-duration: 20s

接口添加注解

第一个参数name表示你的舱壁隔离用在哪,fallbackMethod兜底策略将会执行自定义的兜底方法,type舱壁隔离的类型
    @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)

2. 固定线程池舱壁(FixedThreadPoolBulkhead)

固定好线程数量,可以解决并发问题(浏览器能够同时访问的数量)

实现:

yml修改:

thread-pool-bulkhead:
    configs:
      default:
        core-thread-pool-size: 1
        max-thread-pool-size: 1
        queue-capacity: 1
    instances:
      cloud-payment-service:
        baseConfig: default

添加新的接口:

/**
 * 根据ID进行Feign调用,使用固定线程池舱壁模式,限制并发执行的数量以保护系统稳定性和可靠性。
 * 当请求到达时,线程池舱壁将控制并发线程数,并在系统负载高时提供降级处理。
 * 
 * @param id 请求的ID
 * @return 返回一个CompletableFuture,异步地执行Feign调用并返回结果
 */
@GetMapping(value = "/feign/pay/bulkhead/{id}")
@Bulkhead(name = "cloud-payment-service", fallbackMethod = "myBulkheadPoolFallback", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id) {
    System.out.println(Thread.currentThread().getName() + "\t" + "进入方法!!!");
    try { 
        // 模拟业务处理耗时
        TimeUnit.SECONDS.sleep(3); 
    } catch (InterruptedException e) { 
        e.printStackTrace(); 
    }
    System.out.println(Thread.currentThread().getName() + "\t" + "退出方法!!!");

    // 异步执行Feign调用并返回结果
    return CompletableFuture.supplyAsync(() -> payFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL");
}

/**
 * 固定线程池舱壁模式的降级处理方法。
 * 当系统负载高或请求失败时,会调用此方法返回预设的降级响应。
 * 
 * @param id 请求的ID
 * @param t 异常信息,用于日志记录或进一步处理
 * @return 返回一个CompletableFuture,包含预设的降级响应
 */
public CompletableFuture<String> myBulkheadPoolFallback(Integer id, Throwable t) {
    return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
}

3.限流

限流(Rate Limiting)是一种控制系统中某个服务或资源被访问的速率的方法。它是一种重要的系统设计策略,用于确保系统不会因为过多的请求而崩溃或性能下降。

主要目的和原理:

  1. 保护系统稳定性:限流可以防止某个服务或资源被过多的请求压垮,从而保护系统的稳定性和可靠性。
  2. 防止雪崩效应:在系统压力激增时,通过限流可以控制并发请求的数量,避免因大量请求同时到达而导致系统全部崩溃。
  3. 平滑流量:通过限流,可以平滑系统的流量,使得系统能够更加可控地处理请求,预防突发流量对系统的冲击。

实现方式:

限流可以通过多种策略和算法实现,常见的包括:

  • 固定窗口计数:在固定时间窗口内限制请求的数量。
  • 滑动窗口计数:随着时间流逝动态调整允许通过的请求数量。
  • 令牌桶算法:通过令牌的发放速率来控制允许通过的请求速率。(默认算法)
  • 漏桶算法:类似于令牌桶,但是以固定的速率漏掉多余的请求。

说白了就是控制相应时间内访问次数

添加依赖

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-ratelimiter</artifactId>
</dependency>

改yml:

####resilience4j ratelimiter 限流的例子
resilience4j:
  ratelimiter:
    configs:
      default:
        limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
        # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
        limitRefreshPeriod: 1s 
        timeout-duration: 1 # 线程等待权限的默认等待时间
    instances:
        cloud-payment-service:
          baseConfig: default

接口注解:

@RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")

和上面两个注解差不多,就是应用场景换一下而已

可以去看看算法到底是如何实现的加深印象