Skip to content

Resilience4j从零到生产级掌握

阅读入口:先建立模块组合顺序

按“许可(RateLimiter/Bulkhead)→ Deadline(TimeLimiter)→ 重试(Retry)→ 调用 → 统计窗口 → 熔断状态机”阅读。每个模块只解决一个资源问题,组合顺序会改变实际尝试次数和指标口径。

Resilience4j不是“一个熔断注解”,而是一组可以组合的稳定性模块。CircuitBreaker负责根据已完成调用的统计结果决定是否继续放行,Retry负责有限重试,Bulkhead负责并发隔离,RateLimiter负责按时间发放许可,TimeLimiter负责限制异步等待。它们职责不同,不会因为加了@CircuitBreaker就自动获得超时、线程池、重试和限流。

本页先建立从零可理解的主线。源码状态机、滑动窗口Subtract-on-Evict、CAS并发转换、Spring AOP顺序、Spring Cloud CircuitBreaker执行链、完整商业Demo、故障窗口和Runbook见Resilience4j内部原理与生产治理;面试复习见Resilience4j独立面试题

核心原理:装饰器组合与熔断状态机

Resilience4j 把一次远程调用包装成多个装饰器:先判断限流或舱壁许可,再执行带超时的调用和有限重试,最后把成功、异常、慢调用写入滑动窗口。窗口统计结果驱动 CLOSED → OPEN → HALF_OPEN 状态转换。

mermaid
flowchart TD
    A["业务调用"] --> B["RateLimiter/Bulkhead获取许可"]
    B --> C["TimeLimiter设置Deadline"]
    C --> D["Retry有限重试与退避"]
    D --> E["HTTP/RPC真实调用"]
    E --> F["记录成功、失败、慢调用"]
    F --> G["滑动窗口聚合指标"]
    G --> H{"失败率或慢调用率达阈值?"}
    H -- "否" --> I["保持 CLOSED"]
    H -- "是" --> J["切换 OPEN,快速失败"]
    J --> K["等待窗口后 HALF_OPEN 探测"]

超时不等于取消底层任务,重试不等于幂等;这些边界决定了治理组件只能控制调用方资源,不能自动回滚下游事务。

一、先区分JDK 8存量线与Java 17现代线

技术线可复核样本字节码用途
JDK 8存量线Resilience4j 1.7.1major 52维护Boot 2/JDK 8系统,不能复制Boot 3配置类
Java 17现代线Boot 3.5.16、Cloud 2025.0.3、Spring Cloud CircuitBreaker 3.3.3、Resilience4j 2.2.0major 61当前Spring Cloud新项目样本

截至2026-07-19,Maven Central上的独立resilience4j-spring-boot3最新发布版是2.4.0;但Cloud 2025.0.3 BOM实际把Spring Cloud CircuitBreaker管理为3.3.3,并解析Resilience4j 2.2.0。新版本号更大不代表可以绕过BOM单独覆盖:Spring适配器、自动配置、Micrometer、Reactor和模块API需要作为组合验证。

不同Boot 3.x小版本对应不同Cloud发布列车。本文用一组真实解析并编译的样本解释原理,不把3.5.16属性当成所有未来版本的永久契约。实际项目先确定Java版本,再核对Boot、Cloud、Alibaba和组件BOM。

二、学习目标

学完应能独立回答:

  1. 慢依赖为什么会从局部故障扩散为线程、连接和实例雪崩。
  2. CircuitBreaker、Retry、Bulkhead、RateLimiter和TimeLimiter各自只解决什么。
  3. CLOSED、OPEN、HALF_OPEN、FORCED_OPEN、DISABLED和METRICS_ONLY有什么区别。
  4. 最小调用数、滑动窗口、失败率和慢调用率怎样共同决定开路。
  5. 次数窗口和时间窗口为什么都能O(1)获取快照。
  6. SemaphoreBulkhead和ThreadPoolBulkhead怎样隔离,代价是什么。
  7. TimeLimiter超时后为什么底层HTTP、SQL和事务可能仍在执行。
  8. Retry为什么会制造流量放大,以及怎样受幂等和总Deadline约束。
  9. Spring Cloud CircuitBreaker门面与原生Resilience4j注解有什么区别。
  10. 多个注解的Aspect顺序为什么改变物理Attempt和统计口径。
  11. 如何配置、测试、监控、灰度和排查熔断、拒绝与降级激增。

三、为什么需要稳定性保护

假设库存服务从50ms变成5秒:

mermaid
flowchart TD
    A["库存服务因锁等待变慢"] --> B["订单服务在途请求增加"]
    B --> C["HTTP连接与业务线程被占满"]
    C --> D["请求进入队列继续等待"]
    D --> E["上游超时并发起重试"]
    E --> F["物理流量进一步放大"]
    F --> G["订单实例也失去响应能力"]
    G --> H["故障扩散为微服务雪崩"]

熔断器的价值是“已知下游持续异常时,不再让每个请求都付出完整失败成本”。但熔断器只能减少未来调用,已经占住的线程、正在执行的SQL、没有超时的Socket和无限队列不会自动消失,因此必须组合多层保护。

四、五个核心模块的职责边界

模块核心问题典型拒绝/异常不自动解决
CircuitBreaker下游持续失败或变慢时还要不要调用CallNotPermittedException超时、线程隔离、重试
Retry瞬时失败是否再尝试最终原异常或重试耗尽异常幂等、Deadline、限流
Bulkhead同时最多允许多少调用占资源BulkheadFullException失败率判断、速率限制
RateLimiter一个时间周期发放多少许可RequestNotPermitted在途并发、下游健康判断
TimeLimiter最多等待Future多久TimeoutException底层I/O必然取消、事务回滚
mermaid
flowchart TD
    A["逻辑请求进入"] --> B["RateLimiter检查时间许可"]
    B --> C["Bulkhead检查并发容量"]
    C --> D["CircuitBreaker检查是否放行"]
    D --> E["Retry执行受限物理尝试"]
    E --> F["TimeLimiter限制异步等待"]
    F --> G["返回事实结果或明确降级"]

这张图只是便于建立职责地图,不代表所有项目都必须使用该固定装饰顺序。Retry放在CircuitBreaker外层还是内层,会改变每个物理尝试是否分别进入熔断统计;注解实际顺序要以目标版本的Aspect Order和集成测试为证据。

五、CircuitBreaker状态机

mermaid
flowchart TD
    A["CLOSED放行并统计"] --> B["样本满足后检查失败率和慢调用率"]
    B --> C["未超阈值则继续CLOSED"]
    C --> D["超阈值则进入OPEN快速拒绝"]
    D --> E["等待时间到期"]
    E --> F["HALF_OPEN发放有限探测"]
    F --> G["探测恢复则CLOSED;未恢复则OPEN"]

C和D是阈值判断的互斥结果,G包含恢复判断的两个去向;用纵向阶段表达可避免移动端状态回边把图撑宽。

5.1 CLOSED

正常放行,记录成功、失败、慢成功、慢失败。只有窗口内调用数达到minimumNumberOfCalls后,失败率或慢调用率才有资格触发开路,否则低流量接口一次失败就可能被误判为100%故障。

5.2 OPEN

不访问下游,直接拒绝。OPEN只表示本实例的本地统计认为下游不值得继续尝试,不表示所有应用实例同时OPEN,也不证明下游已经宕机。

5.3 HALF_OPEN

只发放permittedNumberOfCallsInHalfOpenState个探测许可。若探测恢复则回CLOSED,仍超阈值则回OPEN。有限探测避免下游刚恢复就被全部积压流量再次冲垮。

5.4 其他状态

状态行为使用风险
FORCED_OPEN强制拒绝运维开关忘记恢复会长期不可用
DISABLED放行且不按正常方式熔断故障期间失去保护
METRICS_ONLY放行并统计,但不自动开路适合上线前观察,不能误认为已启用保护

六、滑动窗口不是保存所有请求明细

次数窗口保存最近N次调用的聚合测量;时间窗口按秒维护N个Bucket。两者都维护预聚合总量:新结果进入时累加,最旧测量或Bucket淘汰时从总量减掉,即Subtract-on-Evict。

mermaid
flowchart TD
    A["新调用完成"] --> B["记录成功、失败、慢调用与耗时"]
    B --> C["累加到当前测量和总聚合"]
    C --> D["窗口前移"]
    D --> E["减去被淘汰Bucket的聚合"]
    E --> F["O(1)读取当前Snapshot"]

它不会为了计算失败率每次扫描所有请求对象,因此获取Snapshot与窗口大小近似无关。次数窗口空间复杂度O(N);时间窗口维护N个秒级部分聚合和一个总聚合,不按QPS保存每条调用元组。

七、Bulkhead舱壁隔离

7.1 SemaphoreBulkhead

内部使用Semaphore控制许可,调用仍在当前请求线程执行。它开销较小,但如果下游阻塞,当前Tomcat线程仍会被占用。maxConcurrentCalls限制并发,maxWaitDuration=0表示拿不到许可立即拒绝。

7.2 ThreadPoolBulkhead

使用固定边界的ThreadPoolExecutor和有界队列;队列为0时使用SynchronousQueue,否则使用ArrayBlockingQueue。线程和队列都满时拒绝。它能隔离调用线程,但带来线程切换、队列等待、上下文传播和额外内存成本。

mermaid
flowchart TD
    A["调用提交ThreadPoolBulkhead"] --> B["检查核心线程和队列容量"]
    B --> C["无容量则抛BulkheadFullException"]
    C --> D["有容量则包装上下文并进入执行器"]
    D --> E["执行下游调用"]
    E --> F["完成Promise并发布事件"]

C和D是互斥结果,无容量时不会继续执行D;纵向排列用于保证小屏完整展示。

队列不能越大越好。请求在队列中等待时,用户Deadline可能已经耗尽;稍后出队仍访问下游会形成“幽灵流量”。生产必须同时限制队列长度和等待时间,并监控active、queue、rejected和真实连接池。

八、TimeLimiter只限制等待

Future,TimeLimiter调用带超时的future.get(timeout);超时时可调用future.cancel(true)。但线程中断只是协作信号:Socket库、JDBC驱动、远端服务和业务代码可能忽略中断。

CompletionStage,它通过调度器安排一个超时任务,超时时把CompletableFuture异常完成;这同样不等于远端副作用停止。

因此:

  • HTTP客户端仍要配置Connect、Pool Acquire和Read/Response Timeout。
  • SQL仍要有Query Timeout和数据库侧资源治理。
  • 写操作超时后结果是UNKNOWN,不是FAILED。
  • 库存、支付、订单需要幂等键、状态查询和补偿。

九、Retry为什么最容易制造事故

maxAttempts=3通常表示首次调用加最多两次再次尝试,不是“首次调用后再重试三次”。同步Retry在再次尝试之间会按IntervalFunction等待;异步Retry通过CompletionStage和调度器安排下一次尝试。

可以重试的典型情况:

  • 建连前明确失败。
  • 幂等查询遇到短暂连接错误。
  • 服务端明确返回可重试状态并给出Retry-After
  • 已确认回滚的数据库死锁事务。

不应盲目重试:

  • 参数错误、权限失败、库存不足。
  • 无幂等保护的写操作。
  • 结果未知的支付和扣减。
  • 已没有剩余Deadline。
  • 下游整体过载。

网关2次、Mesh2次、Feign3次会把一次逻辑请求放大到最坏12次物理Attempt。应只保留一个主要重试层,使用指数退避、抖动、总Deadline和Retry Budget。

十、RateLimiter与Bulkhead不是一回事

RateLimiter控制“单位时间允许多少”,Bulkhead控制“同时有多少正在执行”。100 QPS且每次10秒,会产生约1000个在途请求;即使速率没有超限,也可能耗尽线程和连接。

Resilience4j的AtomicRateLimiter把时间划分为Cycle,用AtomicReference<State>保存活动周期、可用许可和需要等待的纳秒数,通过CAS更新。许可可以为未来周期预留,所以内部activePermissions可能为负数;这不代表产生了负容量,而是已经预订后续周期许可。

十一、两种Spring集成方式

方式核心入口优点边界
Spring Cloud CircuitBreakerCircuitBreakerFactory统一Spring Cloud门面,便于替换实现门面不暴露全部原生模块能力
原生Resilience4jRegistry、Decorators、@CircuitBreaker模块、事件和配置能力完整注解依赖AOP,组合顺序需验证

Spring Cloud starter主要提供CircuitBreaker和TimeLimiter集成。若要直接使用原生RetryBulkheadRateLimiter模块,应显式加入相应模块并由兼容BOM管理版本;使用注解还要有spring-boot-starter-aop

十二、Java 17与Boot 3最小Demo

12.1 Maven依赖

xml
<properties>
    <java.version>17</java.version>
    <spring-cloud.version>2025.0.3</spring-cloud.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

12.2 业务调用

java
package com.example.order;

import java.util.function.Function;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.stereotype.Service;

@Service
public class InventoryGateway {
    private final CircuitBreakerFactory<?, ?> factory;
    private final InventoryHttpClient client;

    public InventoryGateway(CircuitBreakerFactory<?, ?> factory,
                            InventoryHttpClient client) {
        this.factory = factory;
        this.client = client;
    }

    public InventoryView query(String skuId) {
        CircuitBreaker breaker = factory.create("inventory-query", "inventory-service");
        Function<Throwable, InventoryView> fallback = error ->
                new InventoryView(skuId, InventoryStatus.UNKNOWN, true);
        return breaker.run(() -> client.query(skuId), fallback);
    }
}

Fallback返回UNKNOWN并标记degraded=true,而不是伪造“库存为0”或“库存充足”。降级结果必须进入接口契约、日志、Trace和指标,避免上游把默认值持久化成业务事实。

十三、商业订单聚合场景

订单详情聚合订单主数据、库存、优惠、物流和推荐:

依赖业务级别失败策略
订单主数据核心失败则整体失败,不伪造订单
库存展示重要返回UNKNOWN,不等于无库存
优惠推荐可降级返回空列表并明确degraded
物流轨迹可延后返回暂不可用,允许前端稍后刷新
推荐内容非核心直接Load Shedding,保护核心链路

总Deadline为1200ms时,不能给每个下游都配置1200ms再重试三次。应并行调用,分配子预算,为每个故障域使用独立Bulkhead和CircuitBreaker,并让Fallback保持简单、快速、无远程依赖。

十四、常见错误

错误做法为什么错
@CircuitBreaker就认为有超时CircuitBreaker不负责Timeout
所有异常都计为失败业务拒绝、404、429、连接失败语义不同
写操作超时后直接重试下游可能已经提交,造成重复副作用
Bulkhead队列无限增大把快速拒绝变成长排队和幽灵流量
Fallback返回伪成功污染业务事实并掩盖事故
每层都Retry物理Attempt乘法放大
只看失败率不看慢调用全部成功但耗时10秒仍可雪崩
强制覆盖Cloud BOM里的Resilience4j版本可能破坏自动配置和适配器兼容
用本地RateLimiter做全局配额每实例独立,扩容后总许可增加
Java 17项目宣称使用虚拟线程虚拟线程在Java 21正式发布

十五、继续学习

本章小结

Resilience4j的正确心智模型是“多个职责单一的保护器按明确顺序包装真实调用”。真正掌握它,不只是会写注解,而是能解释每个许可在哪里获取、调用结果怎样进入滑动窗口、状态如何并发转换、超时后什么还在运行、重试会放大多少物理流量,以及故障恢复时怎样避免再次冲垮下游。