Spring Boot 4.0 限流、熔断与重试:Resilience4j 实战
前言
远程服务变慢时,最常见的错误做法是“加一个重试”。如果下游已经过载,重试会继续放大流量;如果不设上限,调用方线程和连接会被拖死。
本章为订单风控调用组合三道防线:请求限流、有限重试和熔断。连续失败达到阈值后直接降级,不再访问下游。
读者需要完成上一篇异步聚合教程。源码位于 dvsusan/spring-boot4-demo。
最终结果
新增接口:
GET /api/v1/chapter7/resilience/risk/{orderNo}?fail=false
GET /api/v1/chapter7/resilience/statefail=false 返回正常风控结果;fail=true 会模拟失败,依次触发重试、失败计数和熔断打开。

图 1:限流控制进入速度,熔断阻止持续失败,重试只处理有限范围内的瞬时异常。
第 1 步:添加 Resilience4j 模块
<!-- 只需要熔断、限流和重试核心模块,不必引入整套 Web 依赖。 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>${resilience4j.version}</version>
</dependency>项目固定使用 Resilience4j 2.4.0,避免 Maven 在不同模块解析到不同主版本。
第 2 步:配置失败窗口与限流
resilience4j:
circuitbreaker:
instances:
risk-service:
# 最近 4 次调用中,失败率达到 50% 才可能打开。
sliding-window-size: 4
minimum-number-of-calls: 4
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
ratelimiter:
instances:
chapter7-risk:
limit-for-period: 5
limit-refresh-period: 1s
timeout-duration: 100ms
retry:
instances:
risk-service:
max-attempts: 3
wait-duration: 100ms熔断阈值不能照抄。窗口太小容易被偶发错误打开,窗口太大又会延迟保护。应该结合调用量、超时率和恢复时间观测。
第 3 步:在代码中组合策略
if (!rateLimiter.acquirePermission()) {
return RiskResult.fallback(
orderNo,
"RATE_LIMITED",
circuitBreaker.getState().name()
);
}
Supplier<RiskResult> downstream = () -> {
if (fail) {
throw new IllegalStateException("风控服务超时");
}
return new RiskResult(orderNo, "LOW", true, false, 0,
circuitBreaker.getState().name());
};
Supplier<RiskResult> protectedCall = CircuitBreaker.decorateSupplier(
circuitBreaker,
Retry.decorateSupplier(retry, downstream)
);当前顺序是限流放在最外层,熔断包住重试。这样没有拿到限流许可的请求不会增加熔断器失败计数,重试耗尽后才作为一次下游失败进入熔断统计。
第 4 步:返回稳定降级结果
try {
return protectedCall.get();
} catch (CallNotPermittedException exception) {
return RiskResult.fallback(
orderNo,
"CIRCUIT_OPEN",
circuitBreaker.getState().name()
);
} catch (RuntimeException exception) {
return RiskResult.fallback(
orderNo,
"DOWNSTREAM_FAILED",
circuitBreaker.getState().name()
);
}降级结果必须告诉调用方停用原因。所有异常都返回同一个 null,后续无法区分“被限流”“熔断打开”和“下游失败”。
第 5 步:启动并验证
java -jar demo-web/target/demo-web-1.0.0-SNAPSHOT.jar \
--spring.profiles.active=chapter7 \
--server.port=18107
curl -sS \
"http://localhost:18107/api/v1/chapter7/resilience/risk/SO1001?fail=false"连续提交失败请求:
for i in 1 2 3 4 5; do
curl -sS \
"http://localhost:18107/api/v1/chapter7/resilience/risk/SO1001?fail=true"
printf '\n'
# 避免连续请求被每秒 5 次的限流器误判,单独观察熔断状态转换。
sleep 1
done
curl -sS \
http://localhost:18107/api/v1/chapter7/resilience/state前四次返回 DOWNSTREAM_FAILED 并累计失败,第五次在熔断器已打开后直接返回 CIRCUIT_OPEN。如果本机请求过快,中间可能出现 RATE_LIMITED,它表示请求没有进入熔断统计,这是两套策略的正常分工。
第 6 步:自动化验证
mvn -pl demo-web -am \
-Dtest=Chapter7AdvancedIntegrationTest \
-Dsurefire.failIfNoSpecifiedTests=false \
test测试将限流阈值临时调高,只验证熔断核心逻辑,避免测试被测试机器的执行速度影响。实际结果为 4 个用例通过,其中连续四次失败后第五次进入熔断降级。
重试前先确认错误类型
只应重试瞬时网络错误、短暂超时和明确的“稍后重试”响应。参数错误、鉴权失败、余额不足和业务拒绝重复执行没有意义,甚至会产生副作用。
支付、扣款等写操作还必须同时使用幂等键。重试不是幂等机制的替代品。
常见问题排查
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 熔断器一直不打开 | 失败率未达到阈值或调用数不足 | 检查最小调用数和窗口 |
| 所有请求都被限流 | 本机时间或配额配置错误 | 查看 RateLimiter 指标和 refresh period |
| 重试导致流量翻倍 | 对过载服务无限重试 | 限制次数、退避并配合熔断 |
| 熔断打开后仍访问下游 | 只使用 Retry 没有使用 CircuitBreaker | 组合调用链并检查顺序 |
| 降级掩盖真实故障 | 异常只返回默认值 | 返回稳定错误码并记录指标和日志 |
练习题
把固定失败模拟改成两个下游客户端:一个超时、一个返回业务拒绝。要求只对超时重试,业务拒绝直接返回;连续超时触发熔断。验收标准是两类失败的指标和响应码可区分。
总结
限流保护系统入口,熔断保护故障下游,重试处理有限瞬时失败。三种策略组合时必须明确顺序、错误类型和降级语义。
下一篇从接口层回到整体性能:连接池、序列化和分页如何共同影响响应时间。
上一节:CompletableFuture 与虚拟线程
下一节:企业级性能调优
相关阅读:Redis 缓存设计
返回课程目录
13 个企业级实战项目 · 源码 + 教程 + 答疑
简历包装指导AI Agent 实战高并发·微服务Offer 导向持续更新3000+ 同行交流1V1 答疑3 天包退
覆盖高并发、微服务、分布式与 AI Agent:100万QPS短链、企业智能知识库、商城微服务、SaaS点餐、秒杀、刷题小程序、代码生成器、AI Agent 等。券后 ¥129(原价 ¥159),不满意 3 天包退。
