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%即熔断。
定义被保护的服务和降级方法
在 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 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_SERVICE或DOWN。 -
熔断器事件 :
http://localhost:8080/actuator/circuitbreakerevents。这里会记录熔断器状态变更、失败调用等事件,是排查问题的利器。
⚠️: @CircuitBreaker 注解需要Spring AOP代理才能生效。这意味着在同一个类内部调用被注解的方法(如 this.protectedCall())会导致熔断失效,这一点在实际开发中要格外留心。我是踩过坑的哦。
⚠️:@TimeLimiter 注解只能用在返回 CompletableFuture的方法上。 因为超时控制需要通过异步非阻塞的方式来实现,如果方法直接返回业务结果,@TimeLimiter 是无法生效的。
⚠️:线程池选择 :@TimeLimiter 内部会使用 CompletableFuture.supplyAsync() 的默认线程池(ForkJoinPool.commonPool())。生产环境建议显式指定自定义的线程池,避免默认线程池被耗尽。