本文深入解析 resilience4j 多重注解(@retry、@circuitbreaker、@timelimiter、@bulkhead)的嵌套执行顺序,结合源码级调用链说明异常传播路径,并提供基于异常类型、业务结果和自定义谓词的精准重试控制方案。
本文深入解析 resilience4j 多重注解(@retry、@circuitbreaker、@timelimiter、@bulkhead)的嵌套执行顺序,结合源码级调用链说明异常传播路径,并提供基于异常类型、业务结果和自定义谓词的精准重试控制方案。
在 Spring Boot 项目中集成 Resilience4j 实现高可用下游调用时,理解各容错组件的执行顺序与异常拦截边界至关重要。根据官方文档与 AOP 代理链机制,Resilience4j 的注解按如下由外至内顺序织入(即最外层最先触发,最内层最接近实际业务逻辑):
@Retry
└── @CircuitBreaker
└── @TimeLimiter
└── @Bulkhead
└── 实际方法调用(如 downstream1())这意味着:@Retry 是最外层“守门人”,它仅对内部所有组件抛出的异常(或返回值)做统一决策;而 @Bulkhead 是最内层“执行沙箱”,负责线程隔离与资源准入控制。
✅ 正确的执行逻辑与异常流向(对应你的用例)
你对各场景下是否触发重试的理解基本正确,但需补充关键细节以确保行为可控:
| 场景 | 触发组件 | 抛出异常类型 | @Retry 是否重试? | 原因 |
|---|---|---|---|---|
| 线程池满 | @Bulkhead | BulkheadFullException | ❌ 否 | 属于资源限制类异常,非业务失败 |
| 超时 | @TimeLimiter | TimeoutException | ❌ 否(默认) | 需显式配置 retryExceptions 才会重试 |
| 断路器开启 | @CircuitBreaker | CallNotPermittedException | ❌ 否 | 表示主动熔断,重试无意义 |
| 下游 HTTP 5xx 错误 | 方法体抛出 HttpServerErrorException | ✅ 是(需配置) | 默认不重试,必须通过 retryExceptions 显式声明 | |
| 其他未声明异常(如 NPE) | 方法体抛出任意 RuntimeException | ❌ 否(默认) | @Retry 默认仅重试 Exception 及子类,且需白名单 |
⚠️ 关键提醒:@Retry 默认不会重试任何异常——它依赖显式配置的 retryExceptions 白名单。未配置时,即使发生 IOException 或 HttpServerErrorException,也不会重试。
✅ 推荐配置方案(适配你的需求)
为严格满足「仅在下游服务端错误(如 HTTP 5xx)时重试,其余场景(超时/熔断/线程池满)均跳过重试」,应在 application.yml 中进行如下精细化配置:
resilience4j.retry:
configs:
default:
max-attempts: 3
wait-duration: 100ms
# ✅ 仅对服务端错误重试(如 500、502、503、504)
retry-exceptions:
- org.springframework.web.client.HttpServerErrorException
- org.springframework.web.client.ResourceAccessException # 如连接被拒、DNS失败
# ❌ 显式忽略客户端错误(如 4xx)、超时、熔断、线程池满等
ignore-exceptions:
- java.util.concurrent.TimeoutException
- io.github.resilience4j.circuitbreaker.CallNotPermittedException
- io.github.resilience4j.bulkhead.BulkheadFullException
- org.springframework.web.client.HttpClientErrorException # 4xx 不重试同时,移除方法级 @Retry 注解中的 fallbackMethod(因其与 @CircuitBreaker/@TimeLimiter 的 fallback 冲突),统一由最外层 @Retry 或组合 fallback 处理:
@Override
@Bulkhead(name = "downstream1", type = Type.THREADPOOL)
@TimeLimiter(name = "downstream1")
@CircuitBreaker(name = "downstream1")
@Retry(name = "downstream1") // ✅ 仅保留注解,不设 fallbackMethod
public CompletableFuture<String> downstream1(String a) {
return CompletableFuture.completedFuture(process(a));
}
// ✅ 统一 fallback(由 @Retry 最终触发)
public CompletableFuture<String> downstream1Fallback(String a, Throwable throwable) {
log.warn("Downstream1 failed with fallback, cause: {}", throwable.getMessage());
return CompletableFuture.completedFuture(""); // 或降级数据
}✅ 进阶:基于返回结果的动态重试(可选)
若需更灵活策略(例如:下游返回 { "code": 500 } JSON 也应重试),可实现 retryOnResultPredicate:
@Component
public class DownstreamErrorResultPredicate implements Predicate<Object> {
@Override
public boolean test(Object result) {
if (result instanceof String str && str.contains("\"code\":500")) {
return true; // 符合重试条件
}
return false;
}
}并在配置中启用:
resilience4j.retry:
configs:
default:
retry-on-result-predicate: com.example.predicates.DownstreamErrorResultPredicate✅ 总结:最佳实践清单
- ✅ 顺序不可变:@Retry 必须是最外层注解,才能捕获所有内层异常;
- ✅ 白名单驱动:retryExceptions 是重试开关,务必显式配置服务端异常类型;
- ✅ 黑名单兜底:ignoreExceptions 可防止误重试熔断/超时等非业务异常;
- ✅ Fallback 统一化:避免多层 fallback 嵌套,推荐由 @Retry 单点接管;
- ✅ 异步兼容性:CompletableFuture 返回值天然支持 @TimeLimiter 和 @Bulkhead(线程池模式),无需额外适配。
通过以上配置,你的 downstream1() 将严格遵循:仅当真实业务调用抛出 HttpServerErrorException 等指定异常时触发重试;其余所有容错组件拦截场景均直接走 fallback,绝不重试——完美契合高可用治理诉求。

















