在springboot中使用Resilience4j

Netflix Hystrix已进入维护期,Resilience4j是其官方推荐的替代品,设计更轻量、更现代化。

先引入相关maven依赖(包括actuator监控):--

缺少spring-boot-starter-aop 会让 @CircuitBreaker 这类注解实效

复制代码
<!-- Resilience4j Spring Boot Starter (核心) -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.7.1</version>
</dependency>

<!-- Spring AOP 支持 (注解生效的关键) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- Spring Boot Actuator (监控熔断器状态) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

代码应用

复制代码
import io.github.resilience4j.circuitbreaker.*;
import java.time.Duration;

// 1. 配置熔断器(适配RTB场景)
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .slidingWindowType(SlidingWindowType.TIME_BASED)  // 基于时间窗口统计
    .slidingWindowSize(10)                            // 统计窗口10秒
    .minimumNumberOfCalls(20)                         // 至少20个请求才判断
    .failureRateThreshold(50)                         // 失败率>50%触发熔断
    .slowCallRateThreshold(60)                        // 慢请求率>60%触发熔断
    .slowCallDurationThreshold(Duration.ofMillis(45)) // 慢请求定义=超过45ms
    .waitDurationInOpenState(Duration.ofSeconds(30))  // 熔断持续30秒
    .permittedNumberOfCallsInHalfOpenState(5)         // 半开状态允许5个探测请求
    .build();

// 2. 创建熔断器实例(可配合registry统一管理)
CircuitBreaker circuitBreaker = CircuitBreaker.of("rtb-bidding", config);

// 3. 装饰竞价函数
Supplier<BidResponse> decoratedSupplier = CircuitBreaker
    .decorateSupplier(circuitBreaker, () -> doBiddingWithTimeout(request));

// 4. 执行(熔断器自动统计并保护)
try {
    return decoratedSupplier.get();
} catch (CircuitBreakerOpenException e) {
    // 熔断器处于OPEN状态,直接放弃竞价
    return BidResponse.noBid("circuit_breaker_open");
}
  • slowCallDurationThreshold(45ms):与你业务超时阈值完全对齐,超过45ms的请求会计入"慢调用"。

  • failureRateThreshold:超时、异常、慢调用都算失败,累计失败率超过50%即熔断。

定义被保护的服务和降级方法

ServiceComponent 中,使用注解标记需要保护的方法,并定义降级方法。

一般方法使用:

复制代码
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class ExternalApiService {

    // 模拟调用外部API
    public String callExternalApi() {
        // 这里可以是 RestTemplate 或 Feign 调用
        // 为了演示,我们直接抛出异常
        throw new RuntimeException("外部服务调用失败");
    }

    /**
     * 被熔断器保护的方法
     * name: 熔断器实例名,需与配置文件中的名称一致
     * fallbackMethod: 降级方法名
     */
    @CircuitBreaker(name = "backendA", fallbackMethod = "fallbackCall")
    public String protectedCall() {
        return callExternalApi();
    }

    /**
     * 降级方法
     * 注意:参数必须与原方法一致,最后加一个 Exception 参数
     */
    public String fallbackCall(Exception ex) {
        // 这里可以返回默认值或缓存数据
        return "服务暂时不可用,这是默认响应";
    }
}

配置熔断器规则 (application.yml)

复制代码
resilience4j.circuitbreaker:
  instances:
    backendA:  # 必须与 @CircuitBreaker(name = "backendA") 保持一致
      # 核心熔断配置
      failure-rate-threshold: 50           # 失败率达到50%时开启熔断
      slow-call-rate-threshold: 60         # 慢调用比例阈值,超过此值也触发熔断
      slow-call-duration-threshold: 45ms   # 定义慢调用的时长:45ms
      sliding-window-type: TIME_BASED      # 滑动窗口类型:基于时间
      sliding-window-size: 10              # 窗口大小:10秒
      minimum-number-of-calls: 10          # 最少请求数,达到这个值后才计算失败率
      permitted-number-of-calls-in-half-open-state: 5 # 半开状态下允许通过的请求数
      wait-duration-in-open-state: 30s     # 熔断器开启后,进入半开状态前的等待时间
      automatic-transition-from-open-to-half-open-enabled: true # 自动从开启转为半开

      # 记录异常,只有这些异常会被计入失败统计 (默认是所有Throwable)
      record-exceptions:
        - org.springframework.web.client.HttpServerErrorException
        - java.io.IOException
        - java.util.concurrent.TimeoutException
      # 忽略异常,这些异常不会触发熔断器状态变更 (例如业务异常)
      ignore-exceptions:
        - com.yourcompany.BusinessException

# Actuator 配置,用于暴露熔断器健康检查和指标
management:
  endpoints:
    web:
      exposure:
        include: health, circuitbreakerevents
  endpoint:
    health:
      show-details: always

这个只是触发熔断的规则slow-call-duration-threshold: 45ms 定义了超过45ms即为慢调用,结合失败率阈值,就会触发熔断。

结合 TimeLimiter 实现严格的超时控制

如果需要对方法执行时间进行硬性限制(比如严格45ms超时),可以将 @TimeLimiter@CircuitBreaker 组合使用。也可以单独使用。

代码如下:

复制代码
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;

@Service
public class ApiService {

    @CircuitBreaker(name = "backendA", fallbackMethod = "fallback")
    @TimeLimiter(name = "backendB")
    public CompletableFuture<String> protectedCallWithTimeout() {
        // 注意:被 @TimeLimiter 保护的方法必须返回 CompletableFuture
        return CompletableFuture.supplyAsync(() -> {
            // 模拟一个可能耗时较长的外部调用
            return callExternalApi();
        });
    }

    public CompletableFuture<String> fallback(Exception ex) {
        return CompletableFuture.completedFuture("超时或熔断,返回默认值");
    }
}

对应的超时配置如下:

复制代码
resilience4j.timelimiter:
  instances:
    backendB:
      timeout-duration: 45ms  # 硬超时限制
      cancel-running-future: true # 超时后是否取消正在执行的任务

限流的使用:

在 Resilience4j 中既可以通过请求数限流也可以通过线程数限流,限流主要通过 RateLimiter (频率限制器)实现,另外还有一个 Bulkhead(舱壁)用于控制并发数。两者都可以用注解或编程式 API 使用。

RateLimiter 控制单位时间内的请求次数,比如"1秒内最多处理100个请求"。

配置如下(也可以把配置通过config在代码中实现):

复制代码
resilience4j.ratelimiter:
  instances:
    # 严格模式:1秒最多100次,超过立即失败
    strictLimiter:
      limit-for-period: 100
      limit-refresh-period: 1s
      timeout-duration: 0ms          # 不等待,直接超时失败
    
    # 宽松模式:500ms最多60次,超时等待10秒
    relaxedLimiter:
      limit-for-period: 60
      limit-refresh-period: 500ms
      timeout-duration: 10s          # 等待10秒获取令牌
    
    #  场景:45ms 窗口,限制频率
    rtbLimiter:
      limit-for-period: 100           # 每45ms最多100个请求
      limit-refresh-period: 45ms
      timeout-duration: 45ms          # 获取令牌超时=直接放弃竞价

|------------------------|-------------------------------------|
| limit-for-period | 每个时间窗口内允许的最大请求数 |
| limit-refresh-period | 时间窗口的刷新周期 |
| timeout-duration | 获取令牌的超时时间。设为 0ms 表示立即失败;设为大于0表示等待 |

代码中使用注解限流:

复制代码
@Service
public class BiddingService {

    @RateLimiter(name = "rtbLimiter", fallbackMethod = "fallback")
    public BidResponse bid(BidRequest request) {
        // 竞价逻辑
        return doBidding(request);
    }

    // 降级方法:限流触发时调用
    public BidResponse fallback(BidRequest request, Exception ex) {
        return BidResponse.noBid("rate_limited");
    }
}

Bulkhead 限制同时执行的线程数,防止某个服务占用过多线程资源。

配置文件如下:

复制代码
resilience4j.bulkhead:
  instances:
    rtbBulkhead:
      max-concurrent-calls: 50      # 最大并发数
      max-wait-duration: 10ms       # 超过并发时的等待时间

代码中使用:

复制代码
@Bulkhead(name = "rtbBulkhead", fallbackMethod = "fallback")
public BidResponse bid(BidRequest request) {
    return doBidding(request);
}

两个注解可以同时使用,请求需要同时满足两种限制才能执行。

复制代码
@Bulkhead(name = "rtbBulkhead")
@RateLimiter(name = "rtbLimiter", fallbackMethod = "fallback")
public CompletableFuture<BidResponse> bid(BidRequest request) {
    return CompletableFuture.supplyAsync(() -> doBidding(request));
}

失败重试

Resilience4j 的 Retry 模块会在方法抛出特定异常时自动重试,直到成功或达到最大重试次数。

适用场景

  • 网络抖动、连接超时等瞬时故障

  • 下游服务短暂不可用(如正在重启)

  • 数据库乐观锁冲突(OptimisticLockingFailureException

⚠️ 注意:重试不适合幂等性要求不明确的操作(如银行转账),也不适合 404、400 等永久性错误。

配置如下(也可以通过代码中编程实现):

复制代码
resilience4j.retry:
  instances:
    dmpRetry:                    # 重试实例名
      maxAttempts: 3             # 最大尝试次数(包含第一次)
      waitDuration: 100ms        # 重试间隔
      retryExceptions:           # 触发重试的异常
        - java.net.SocketTimeoutException
        - java.io.IOException
        - org.springframework.web.client.HttpServerErrorException
      ignoreExceptions:          # 不重试的异常
        - com.example.BusinessException

代码中使用

复制代码
@Service
@Slf4j
public class DmpService {

    @Retry(name = "dmpRetry", fallbackMethod = "fallback")
    public UserProfile getUserProfile(String userId) {
        // 模拟调用 DMP 接口获取用户画像
        return restTemplate.getForObject(
            "http://dmp-service/profile/" + userId, 
            UserProfile.class
        );
    }

    // 降级方法:所有重试都失败后调用
    public UserProfile fallback(String userId, Exception ex) {
        log.warn("DMP调用失败,使用默认画像, userId={}, error={}", userId, ex.getMessage());
        return UserProfile.defaultProfile();  // 返回默认值,放弃精细化竞价
    }
}

总结

场景 推荐方案
限制 QPS(如 1秒100次) @RateLimiter
限制并发数(如最多50线程同时执行) @Bulkhead
既要限频率又要限并发 两个注解一起用
超时控制 @TimeLimiter
熔断控制 @RateLimiter 组合
既要控制超时又要熔断控制 @TimeLimiter + @RateLimiter 组合
失败重试 @Retry

启动Spring Boot应用后,可以通过访问以下Actuator端点来观察熔断器的状态:

  • 健康检查 : http://localhost:8080/actuator/health。当熔断器开启时,健康状态会变为 OUT_OF_SERVICEDOWN

  • 熔断器事件 : http://localhost:8080/actuator/circuitbreakerevents。这里会记录熔断器状态变更、失败调用等事件,是排查问题的利器。

⚠️: @CircuitBreaker 注解需要Spring AOP代理才能生效。这意味着在同一个类内部调用被注解的方法(如 this.protectedCall())会导致熔断失效,这一点在实际开发中要格外留心。我是踩过坑的哦。

⚠️:@TimeLimiter 注解只能用在返回 CompletableFuture的方法上。 因为超时控制需要通过异步非阻塞的方式来实现,如果方法直接返回业务结果,@TimeLimiter 是无法生效的。

⚠️:线程池选择@TimeLimiter 内部会使用 CompletableFuture.supplyAsync() 的默认线程池(ForkJoinPool.commonPool())。生产环境建议显式指定自定义的线程池,避免默认线程池被耗尽。

相关推荐
沐苏瑶2 小时前
Java算法之排序
java·算法·排序算法
java1234_小锋2 小时前
Java高频面试题:Redis是单线程还是多线程?
java·redis·面试
工具罗某人2 小时前
docker实现redis-cluster模式集群部署
java·redis·docker
MgArcher2 小时前
Python高级特性:生成器完全指南
后端
用户3754268434032 小时前
从零构建 Go IM 系统:WebSocket + AI Agent + gRPC 全栈实践
后端
技术爬爬虾2 小时前
OpenCode详细攻略,开源版Claude Code,免费模型与神级插件
前端·后端
邦爷的AI架构笔记2 小时前
GLM-5.1 接入踩坑记录:用免费开源模型搭个 AI 代码审计小工具
后端·算法
Bernard02152 小时前
我试了下最近很火的 Hermes Agent:真正值得看的,不是会调工具,而是会把经验沉淀成 Skill
前端·后端
爱码少年2 小时前
SpringBoot web工程同时启动多个HTTP端口
spring boot