在springboot中使用Resilience4j

张开发
2026/4/9 2:08:27 15 分钟阅读

分享文章

在springboot中使用Resilience4j
Netflix Hystrix已进入维护期Resilience4j是其官方推荐的替代品设计更轻量、更现代化。先引入相关maven依赖包括actuator监控--缺少spring-boot-starter-aop会让CircuitBreaker这类注解实效!-- Resilience4j Spring Boot Starter (核心) -- dependency groupIdio.github.resilience4j/groupId artifactIdresilience4j-spring-boot2/artifactId version1.7.1/version /dependency !-- Spring AOP 支持 (注解生效的关键) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency !-- Spring Boot Actuator (监控熔断器状态) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-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. 装饰竞价函数 SupplierBidResponse 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%即熔断。定义被保护的服务和降级方法在Service或Component中使用注解标记需要保护的方法并定义降级方法。一般方法使用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 CompletableFutureString protectedCallWithTimeout() { // 注意被 TimeLimiter 保护的方法必须返回 CompletableFuture return CompletableFuture.supplyAsync(() - { // 模拟一个可能耗时较长的外部调用 return callExternalApi(); }); } public CompletableFutureString 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 CompletableFutureBidResponse 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组合既要控制超时又要熔断控制TimeLimiterRateLimiter组合失败重试Retry启动Spring Boot应用后可以通过访问以下Actuator端点来观察熔断器的状态健康检查:http://localhost:8080/actuator/health。当熔断器开启时健康状态会变为OUT_OF_SERVICE或DOWN。熔断器事件:http://localhost:8080/actuator/circuitbreakerevents。这里会记录熔断器状态变更、失败调用等事件是排查问题的利器。⚠️CircuitBreaker注解需要Spring AOP代理才能生效。这意味着在同一个类内部调用被注解的方法如this.protectedCall()会导致熔断失效这一点在实际开发中要格外留心。我是踩过坑的哦。⚠️TimeLimiter注解只能用在返回CompletableFuture的方法上。因为超时控制需要通过异步非阻塞的方式来实现如果方法直接返回业务结果TimeLimiter是无法生效的。⚠️线程池选择TimeLimiter内部会使用CompletableFuture.supplyAsync()的默认线程池ForkJoinPool.commonPool()。生产环境建议显式指定自定义的线程池避免默认线程池被耗尽。

更多文章