Skip to content

Resilience4j 详解:弹性容错库

  Resilience4j 是 Spring Cloud 官方推荐的轻量级弹性容错库,专为 Java 函数式编程设计。它提供了熔断器(Circuit Breaker)、限流器(RateLimiter)、重试(Retry)、隔离(Bulkhead)、超时控制(TimeLimiter)、缓存(Cache)等完整的微服务容错能力。

一、为什么需要 Resilience4j?

1.1 微服务的"脆弱性"

一个经典的微服务调用链:

  UI → Gateway → order-service → inventory-service → DB
                                → payment-service  → 第三方支付
                                → sms-service      → 短信网关

问题:只要 inventory-service 变慢,调用它的线程就开始堆积
  → order-service 线程池耗尽
    → Gateway 调用 order-service 也开始超时
      → 整个系统雪崩

任何一个环节出问题,都可能导致全局故障

1.2 Resilience4j 与 Circuit Breaker 的关系

  不是"有了 Circuit Breaker 为什么还需要 Resilience4j",而是——它们根本不是一个层级的东西。

Spring Cloud Circuit Breaker = 接口层(抽象 API)
Resilience4j                  = 实现层(真正干活的那个)

调用关系:
  @CircuitBreaker(name = "orderService")    ← 你写的注解


  Spring Cloud Circuit Breaker 抽象层       ← 统一接口


  Resilience4j CircuitBreaker 实现          ← 真正执行熔断

类比:
  USB 接口 = Circuit Breaker 抽象层
  具体 U 盘 = Resilience4j
  Sentinel  = 另一个 U 盘(也能插 USB 口)

Resilience4j 单独用 vs 通过 Circuit Breaker 抽象层用:

用法代码特点
通过抽象层@CircuitBreaker(name = "x")只暴露熔断能力,可切换到 Sentinel
直接用 Resilience4j@RateLimiter @Retry @Bulkhead访问全部 6 个模块,无法切换实现

结论:Circuit Breaker 抽象层只覆盖熔断这一个能力,Resilience4j 还有限流、重试、隔离、超时、缓存 5 个能力。要么只熔断(用抽象层),要么全套防护(直接用 Resilience4j)。

1.3 为什么取代 Hystrix?

维度HystrixResilience4j
依赖Archaius + RxJava + Hystrix 核心仅需 Vavr(函数式库),极轻量
隔离策略线程池 / 信号量信号量 + 线程池
熔断策略仅失败率失败率 + 慢调用率 + 失败次数
限流内置 RateLimiter
重试内置 Retry
超时内置 TimeLimiter
缓存内置 Cache
配置管理静态动态(运行时修改)
维护状态停维活跃维护

二、核心模块全景

┌──────────────────────────────────────────────────────────────────────┐
│                      Resilience4j 六大模块                            │
│                                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                │
│  │CircuitBreaker│  │  RateLimiter │  │    Retry     │                │
│  │   熔断器      │  │   限流器      │  │   重试器      │                │
│  ├──────────────┤  ├──────────────┤  ├──────────────┤                │
│  │ 状态机        │  │ 令牌桶算法    │  │ 固定间隔/退避 │                │
│  │ 滑动窗口统计  │  │ 超时等待      │  │ 条件重试      │                │
│  │ 半开试探      │  │ 预热模式      │  │ 耗尽策略      │                │
│  └──────────────┘  └──────────────┘  └──────────────┘                │
│                                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                │
│  │   Bulkhead   │  │  TimeLimiter │  │    Cache      │                │
│  │   隔离器       │  │  超时控制     │  │    缓存       │                │
│  ├──────────────┤  ├──────────────┤  ├──────────────┤                │
│  │ 信号量隔离    │  │ 异步超时      │  │ 结果缓存      │                │
│  │ 线程池隔离    │  │ Future 包装  │  │ 异常缓存      │                │
│  │ 饱和策略      │  │              │  │              │                │
│  └──────────────┘  └──────────────┘  └──────────────┘                │
└──────────────────────────────────────────────────────────────────────┘

各模块解决什么问题:

模块解决的问题一句话
CircuitBreaker下游服务挂了,别再调用它了"坏了就断"
RateLimiter调用方请求太快,下游扛不住"快了就限"
Retry网络抖动导致的临时失败"败了就重试"
Bulkhead慢调用占满所有线程资源"分开排队"
TimeLimiter下游响应太慢,别等了"慢了就超时"
Cache重复查询相同数据"查过就缓存"

三、快速入门

3.1 依赖

xml
<!-- 核心依赖(必须) -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- AOP 支持(使用注解必须) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- 可选:Actuator 监控端点 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3.2 第一个例子:给远程调用加熔断

yaml
# application.yml
resilience4j:
  circuitbreaker:
    instances:
      orderService:
        sliding-window-size: 10          # 滑动窗口大小
        failure-rate-threshold: 50       # 失败率阈值 50%
        wait-duration-in-open-state: 5s  # 熔断持续时间
        permitted-number-of-calls-in-half-open-state: 3  # 半开时允许的试探请求数
java
@Service
public class OrderService {

    @CircuitBreaker(name = "orderService", fallbackMethod = "orderFallback")
    public Order getOrder(Long id) {
        // 远程调用 inventory-service
        return restTemplate.getForObject("http://inventory-service/stock/" + id, Order.class);
    }

    // fallback 方法签名必须与原方法一致,最后多一个异常参数
    public Order orderFallback(Long id, Exception e) {
        log.error("获取订单失败, id={}, error={}", id, e.getMessage());
        return new Order(id, "默认值");  // 兜底数据
    }
}

发生了什么?

正常时:
  getOrder(1) → 调用 inventory-service → 成功 → 返回 Order

failures >= 50% 时:
  getOrder(1) → CircuitBreaker 发现熔断已打开 → 直接执行 fallback
                                 → 不会真正调用 inventory-service
                                 → 快速失败,不阻塞线程

5 秒后(半开):
  getOrder(1) → CircuitBreaker 半开状态 → 试探性调用 inventory-service
                                         → 成功 → 关闭熔断,恢复正常
                                         → 失败 → 重新打开熔断

四、Circuit Breaker(熔断器)深度

4.1 状态机详解

                ┌──────────────────────────────────────────┐
                │          Circuit Breaker 状态机            │
                │                                          │
                │     ┌─────────┐                          │
                │     │ CLOSED  │  ← 正常状态               │
                │     │ (关闭)  │    请求正常通过            │
                │     └────┬────┘    统计失败率/慢调用率     │
                │          │                                │
                │          │ 失败率 ≥ threshold             │
                │          │ 或 慢调用率 ≥ threshold        │
                │          ▼                                │
                │     ┌─────────┐                          │
                │     │  OPEN   │  ← 熔断状态               │
                │     │ (打开)  │    所有请求直接拒绝        │
                │     └────┬────┘    抛出 CallNotPermittedException
                │          │                                │
                │          │ wait-duration-in-open-state 到期
                │          ▼                                │
                │     ┌──────────┐                         │
                │     │HALF_OPEN │  ← 半开状态              │
                │     │ (半开)   │    放行少量请求试探       │
                │     └────┬─────┘                         │
                │          │                                │
                │     ┌────┴────┐                           │
                │     │         │                           │
                │   试探成功   试探失败                       │
                │     │         │                           │
                │     ▼         ▼                           │
                │  CLOSED    OPEN                          │
                │  (恢复正常) (重新熔断)                     │
                └──────────────────────────────────────────┘

4.2 三种熔断策略

策略配置项含义触发条件
失败率failure-rate-threshold请求失败数 / 总请求数≥ 阈值(默认 50%)
慢调用率slow-call-rate-threshold慢调用数 / 总请求数≥ 阈值(默认 100%)
失败次数配置 sliding-window-size 配合滑动窗口内失败次数连续失败达到阈值

三种策略详解:

失败率熔断(最常用):
  滑动窗口 10 个请求中有 5 个失败(50%)→ 熔断

慢调用率熔断(保护下游慢服务):
  什么时候算"慢"? → slow-call-duration-threshold: 2s(超过 2 秒算慢调用)
  滑动窗口 10 个请求中有 5 个慢调用(50%)→ 熔断
  场景:inventory-service 没有报错,但响应从 50ms 变成了 5s,拖慢整个链路

失败次数熔断(最简单粗暴):
  配置 sliding-window-size: 100, failure-rate-threshold: 100
  窗口内所有请求都失败时才熔断 → 等同于连续失败 100 次

4.3 滑动窗口原理

Resilience4j 使用计数型环形数组滑动窗口:

  时间轴 →  0s    1s    2s    3s    4s    5s    6s    7s    8s    9s
  窗口1    [ S ] [ S ] [ S ] [ S ] [ S ] [ F ] [ S ] [ F ] [ F ] [ F ]
  窗口2          [ S ] [ S ] [ S ] [ S ] [ F ] [ S ] [ F ] [ F ] [ F ] [ S ]
  窗口3                [ S ] [ S ] [ S ] [ F ] [ S ] [ F ] [ F ] [ F ] [ S ] [ S ]

  S = 成功, F = 失败

  窗口1:失败率 = 4/10 = 40% → 不熔断
  窗口2:失败率 = 4/10 = 40% → 不熔断
  窗口3:失败率 = 3/10 = 30% → 不熔断

  原理:窗口像"滑动"一样随时间和请求移动,只统计最近 N 个请求
  不像固定窗口那样有边界问题

4.4 完整配置

yaml
resilience4j:
  circuitbreaker:
    configs:
      default:
        # === 滑动窗口 ===
        sliding-window-type: COUNT_BASED    # COUNT_BASED 或 TIME_BASED
        sliding-window-size: 100            # 窗口大小(请求数或秒数)
        minimum-number-of-calls: 10         # 最少调用次数(防抖,样本太少不熔断)

        # === 失败率熔断 ===
        failure-rate-threshold: 50          # 失败率阈值(%)
        record-exceptions:                  # 哪些异常算"失败"
          - org.springframework.web.client.HttpServerErrorException
          - java.net.ConnectException
          - java.util.concurrent.TimeoutException
        ignore-exceptions:                  # 哪些异常不算"失败"(不计入统计)
          - com.example.BusinessException   # 业务异常不应触发熔断

        # === 慢调用熔断 ===
        slow-call-rate-threshold: 100       # 慢调用率阈值(%),默认 100
        slow-call-duration-threshold: 60s   # 超过多长时间算"慢调用",默认 60s

        # === 熔断状态 ===
        wait-duration-in-open-state: 60s    # 熔断持续时间
        permitted-number-of-calls-in-half-open-state: 10  # 半开时允许的试探请求数
        automatic-transition-from-open-to-half-open-enabled: true  # 自动从 OPEN 过渡到 HALF_OPEN

    instances:
      orderService:
        base-config: default
        failure-rate-threshold: 30
        wait-duration-in-open-state: 10s
        slow-call-duration-threshold: 3s

4.5 编程式使用

java
@Service
public class OrderService {

    private final CircuitBreaker circuitBreaker;

    public OrderService(CircuitBreakerRegistry registry) {
        this.circuitBreaker = registry.circuitBreaker("orderService");
    }

    public Order getOrder(Long id) {
        // 装饰器模式:把原始调用包装成带熔断保护的操作
        Supplier<Order> supplier = CircuitBreaker.decorateSupplier(
            circuitBreaker,
            () -> restTemplate.getForObject("http://inventory-service/stock/" + id, Order.class)
        );

        try {
            return supplier.get();
        } catch (CallNotPermittedException e) {
            // 熔断器打开,请求被拒绝
            return fallbackOrder(id);
        } catch (Exception e) {
            // 业务异常
            return fallbackOrder(id);
        }
    }

    private Order fallbackOrder(Long id) {
        return new Order(id, "降级数据");
    }
}

4.6 事件监听

java
@Configuration
public class CircuitBreakerEventConfig {

    @PostConstruct
    public void registerEvents(CircuitBreakerRegistry registry) {
        CircuitBreaker cb = registry.circuitBreaker("orderService");

        // 监听状态转换
        cb.getEventPublisher()
            .onStateTransition(event -> {
                log.warn("熔断器状态变更: {} → {}",
                    event.getStateTransition().getFromState(),
                    event.getStateTransition().getToState());
            })
            .onCallNotPermitted(event -> {
                log.warn("请求被熔断器拒绝");
            })
            .onSuccess(event -> {
                log.debug("请求成功, RT={}ms", event.getElapsedDuration().toMillis());
            })
            .onError(event -> {
                log.error("请求失败, RT={}ms, error={}",
                    event.getElapsedDuration().toMillis(),
                    event.getThrowable().getMessage());
            });
    }
}

五、RateLimiter(限流器)深度

5.1 令牌桶算法原理

┌──────────────────────────────────────────────────────────────────┐
│                      令牌桶算法(Token Bucket)                    │
│                                                                  │
│   ┌────────────────────────────────────────────────────────┐    │
│   │  令牌桶(容量 = 100)                                    │    │
│   │  ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐     ┌──┐         │    │
│   │  │令│ │令│ │令│ │令│ │令│ │令│ │令│ ... │令│  ← 每秒补充 │    │
│   │  │牌│ │牌│ │牌│ │牌│ │牌│ │牌│ │牌│     │牌│    N 个令牌  │    │
│   │  └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘     └──┘         │    │
│   └────────────────────────────────────────────────────────┘    │
│                          │                                       │
│                          │ 请求到达 → 取令牌                       │
│                          ▼                                       │
│                    ┌──────────┐                                  │
│                    │ 有令牌?  │                                  │
│                    └────┬─────┘                                  │
│                    有   │   无                                   │
│                     │   │                                       │
│                     ▼   ▼                                       │
│                   放行  等待(timeout-duration)                   │
│                         │                                       │
│                     ┌───┴───┐                                    │
│                     │ 等到?  │                                   │
│                     └───┬───┘                                    │
│                    等到了│没等到                                   │
│                     │   │                                       │
│                     ▼   ▼                                       │
│                   放行  拒绝(RequestNotPermitted)                │
└──────────────────────────────────────────────────────────────────┘

关键理解:

  • 令牌桶有容量上限,意味着可以应对突发流量(桶里攒的令牌 ~ 容量)
  • 补充速率是固定的,长期来看平均 QPS 不会超过 limit-for-period
  • 与漏桶的区别:漏桶处理匀速,令牌桶允许突发

5.2 完整配置

yaml
resilience4j:
  ratelimiter:
    configs:
      default:
        limit-for-period: 100            # 每周期允许的请求数
        limit-refresh-period: 1s         # 刷新周期
        timeout-duration: 500ms          # 等待令牌的超时时间(0 = 不等待,立即拒绝)
        register-health-indicator: true  # 注册健康指标
        event-consumer-buffer-size: 100
    instances:
      createOrder:
        base-config: default
        limit-for-period: 50             # 每秒最多 50 个请求
        timeout-duration: 0              # 拿不到令牌立即拒绝
      queryOrder:
        base-config: default
        limit-for-period: 500            # 每秒最多 500 个请求
        timeout-duration: 100ms          # 可以等 100ms

5.3 注解式使用

java
@RestController
public class OrderController {

    @PostMapping("/order/create")
    @RateLimiter(name = "createOrder", fallbackMethod = "rateLimitFallback")
    public Result createOrder(@RequestBody OrderDTO dto) {
        // 业务逻辑
        return Result.success(orderService.create(dto));
    }

    public Result rateLimitFallback(OrderDTO dto, Exception e) {
        return Result.error("请求过于频繁,请稍后再试");
    }

    @GetMapping("/order/{id}")
    @RateLimiter(name = "queryOrder", fallbackMethod = "queryFallback")
    public Result getOrder(@PathVariable Long id) {
        return Result.success(orderService.getById(id));
    }

    public Result queryFallback(Long id, Exception e) {
        // 查询接口也可以从缓存返回
        return Result.success(cacheService.get(id));
    }
}

5.4 编程式使用

java
@Service
public class OrderService {

    private final RateLimiter rateLimiter;

    public OrderService(RateLimiterRegistry registry) {
        this.rateLimiter = registry.rateLimiter("createOrder");
    }

    public Order create(OrderDTO dto) {
        // 尝试获取令牌
        boolean allowed = rateLimiter.acquirePermission();
        if (!allowed) {
            throw new RateLimitException("请求被限流");
        }

        // 带超时的获取
        // boolean allowed = RateLimiter.waitForPermission(rateLimiter, Duration.ofMillis(500));

        return doCreate(dto);
    }
}

5.5 两种使用场景

场景配置行为
严格限流(秒杀)timeout-duration: 0拿不到令牌立即拒绝,不等待
柔性限流(排队)timeout-duration: 500ms拿不到令牌等 500ms,等到了就放行

六、Retry(重试器)深度

6.1 重试策略全景

┌──────────────────────────────────────────────────────────────────┐
│                        三种重试策略                                │
│                                                                  │
│  固定间隔(Fixed):                                              │
│  请求 ──失败── 等 500ms ──重试── 失败 ── 等 500ms ──重试── 失败  │
│                                                                  │
│  指数退避(Exponential Backoff):                                │
│  请求 ──失败── 等 500ms ──重试── 失败 ── 等 1000ms ──重试── 失败  │
│         ── 等 2000ms ──重试── 失败                                │
│                                                                  │
│  随机间隔(Randomized):                                         │
│  请求 ──失败── 等 300~700ms ──重试── 失败 ── 等 300~700ms ──重试  │
└──────────────────────────────────────────────────────────────────┘

6.2 完整配置

yaml
resilience4j:
  retry:
    configs:
      default:
        max-attempts: 3                    # 最大重试次数(含首次调用)
        wait-duration: 500ms               # 重试间隔
        enable-exponential-backoff: false  # 是否启用指数退避
        exponential-backoff-multiplier: 2  # 退避倍数
        enable-randomized-wait: false      # 是否启用随机间隔
        randomized-wait-factor: 0.5        # 随机因子(0.5 = 50% 偏移)

        retry-exceptions:                  # 需要重试的异常
          - java.net.ConnectException
          - java.net.SocketTimeoutException
          - org.springframework.web.client.ResourceAccessException
        ignore-exceptions:                 # 不重试的异常
          - com.example.BusinessException
          - org.springframework.web.client.HttpClientErrorException  # 4xx 不重试

        retry-on-result-predicate: null    # 根据返回值判断是否重试
        fail-after-max-attempts: false     # 重试耗尽后是否抛出异常

    instances:
      inventoryService:
        base-config: default
        max-attempts: 5
        wait-duration: 1s
        enable-exponential-backoff: true
        exponential-backoff-multiplier: 2
        retry-exceptions:
          - java.net.ConnectException
          - java.util.concurrent.TimeoutException

6.3 注解式使用

java
@Retry(name = "inventoryService", fallbackMethod = "retryFallback")
public Inventory getInventory(Long productId) {
    return restTemplate.getForObject(
        "http://inventory-service/stock/" + productId, Inventory.class);
}

public Inventory retryFallback(Long productId, Exception e) {
    log.error("重试 {} 次后仍失败, productId={}", /* 重试次数 */, productId);
    return new Inventory(productId, 0);  // 返回库存为 0 的兜底数据
}

6.4 编程式使用

java
@Service
public class InventoryService {

    private final Retry retry;

    public InventoryService(RetryRegistry registry) {
        this.retry = registry.retry("inventoryService");
    }

    public Inventory getInventory(Long productId) {
        Supplier<Inventory> supplier = Retry.decorateSupplier(
            retry,
            () -> restTemplate.getForObject("http://inventory-service/stock/" + productId, Inventory.class)
        );

        try {
            return supplier.get();
        } catch (Exception e) {
            return new Inventory(productId, 0);
        }
    }
}

6.5 根据返回值决定是否重试

java
@Bean
public RetryConfig retryConfig() {
    return RetryConfig.custom()
        .maxAttempts(3)
        .waitDuration(Duration.ofMillis(500))
        // 返回 null 也算失败,需要重试
        .retryOnResult(result -> result == null)
        .build();
}

6.6 重试事件监听

java
@PostConstruct
public void retryEvents(RetryRegistry registry) {
    Retry retry = registry.retry("inventoryService");

    retry.getEventPublisher()
        .onRetry(event -> {
            log.warn("第 {} 次重试, 上次失败原因: {}",
                event.getNumberOfRetryAttempts(),
                event.getLastThrowable().getMessage());
        })
        .onSuccess(event -> {
            log.info("重试成功, 重试次数: {}", event.getNumberOfRetryAttempts());
        })
        .onError(event -> {
            log.error("重试耗尽, 最终失败: {}", event.getLastThrowable().getMessage());
        });
}

七、Bulkhead(隔离器)深度

7.1 为什么需要隔离?

没有隔离时:
  ┌──────────────────────────────────────────────┐
  │  order-service 线程池(200 个线程)            │
  │                                              │
  │  ┌──┐ ┌──┐ ┌──┐ ┌──┐ ... ┌──┐               │
  │  │  │ │  │ │  │ │  │     │  │  ← 200 个线程   │
  │  └──┘ └──┘ └──┘ └──┘     └──┘               │
  │   │    │    │    │           │               │
  │   ▼    ▼    ▼    ▼           ▼               │
  │  调用 inventory(慢)  调用 payment(正常)       │
  │                      但是线程被 inventory    │
  │  占满了!线程池耗尽!← 占满了,payment 也调不了│
  └──────────────────────────────────────────────┘

有隔离时:
  ┌──────────────────────────────────────────────┐
  │  order-service                                │
  │                                              │
  │  ┌─────────────────────┐  ┌────────────────┐ │
  │  │ inventory 隔离区     │  │ payment 隔离区  │ │
  │  │ max-concurrent: 10   │  │ max: 50        │ │
  │  │ ┌──┐ ┌──┐ ┌──┐      │  │ ┌──┐ ┌──┐ ┌──┐ │ │
  │  │ │  │ │  │ │  │      │  │ │  │ │  │ │  │ │ │
  │  │ └──┘ └──┘ └──┘      │  │ └──┘ └──┘ └──┘ │ │
  │  │ 10/10 满了!         │  │ 3/50 还有余量   │ │
  │  └─────────────────────┘  └────────────────┘ │
  │                                              │
  │  inventory 慢只影响它自己,payment 不受影响!  │
  └──────────────────────────────────────────────┘

7.2 两种隔离模式

模式实现方式特点适用场景
Semaphore信号量计数器无额外线程,ThreadLocal 自动传递,轻量大部分场景
ThreadPool独立线程池完全隔离,线程切换开销,ThreadLocal 需手动传递需要严格隔离的慢调用

7.3 Semaphore 模式配置

yaml
resilience4j:
  bulkhead:
    configs:
      default:
        max-concurrent-calls: 10            # 最大并发调用数
        max-wait-duration: 0                # 饱和时等待时间(0 = 立即拒绝)
        fair-call-handling-enabled: false   # 公平模式
    instances:
      orderService:
        base-config: default
        max-concurrent-calls: 25
        max-wait-duration: 100ms           # 等 100ms
java
@Bulkhead(name = "orderService", fallbackMethod = "bulkheadFallback")
public Order getOrder(Long id) {
    return restTemplate.getForObject(url, Order.class);
}

public Order bulkheadFallback(Long id, BulkheadFullException e) {
    return new Order(id, "系统繁忙,请稍后重试");
}

7.4 ThreadPool 模式配置

yaml
resilience4j:
  thread-pool-bulkhead:
    instances:
      paymentService:
        core-thread-pool-size: 5            # 核心线程数
        max-thread-pool-size: 10            # 最大线程数
        queue-capacity: 100                 # 队列容量
        keep-alive-duration: 20ms           # 空闲线程存活时间
java
@Bulkhead(name = "paymentService", type = Bulkhead.Type.THREADPOOL, fallbackMethod = "poolFallback")
public CompletableFuture<Payment> pay(Long orderId) {
    return CompletableFuture.supplyAsync(() -> {
        // 在独立线程池中执行
        return restTemplate.postForObject(url, orderId, Payment.class);
    });
}

public CompletableFuture<Payment> poolFallback(Long orderId, Exception e) {
    return CompletableFuture.completedFuture(new Payment(orderId, "FAILED"));
}

7.5 Semaphore vs ThreadPool 选型

场景推荐
调用快(< 100ms)Semaphore
调用慢(> 1s)ThreadPool
需要传递 ThreadLocal(如链路追踪 ID)Semaphore
需要严格隔离,防止慢调用影响其他业务ThreadPool
简单场景,不想引入额外线程Semaphore

八、TimeLimiter(超时控制)深度

8.1 超时控制原理

┌──────────────────────────────────────────────────────────────────┐
│                       TimeLimiter 超时控制                        │
│                                                                  │
│  调用方                      TimeLimiter              下游服务    │
│    │                             │                        │      │
│    │  submit(task)               │                        │      │
│    │────────────────────────────→│                        │      │
│    │                             │  发起调用               │      │
│    │                             │───────────────────────→│      │
│    │                             │                        │      │
│    │                             │  ... 2 秒过去了 ...     │      │
│    │                             │  timeout-duration: 2s  │      │
│    │                             │                        │      │
│    │  TimeoutException           │  超时!                 │      │
│    │←────────────────────────────│                        │      │
│    │                             │                        │      │
│    │  fallback 处理              │  cancel-running-future:│      │
│    │  (返回兜底数据)              │    true → 取消任务      │      │
│    │                             │    false → 任务继续执行 │      │
└──────────────────────────────────────────────────────────────────┘

8.2 配置与使用

yaml
resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 2s               # 超时时间
        cancel-running-future: true        # 超时后取消正在执行的任务
    instances:
      orderService:
        timeout-duration: 3s
        cancel-running-future: true
java
@Service
public class OrderService {

    // TimeLimiter 必须返回 CompletableFuture 或 Future
    @TimeLimiter(name = "orderService", fallbackMethod = "timeoutFallback")
    public CompletableFuture<Order> getOrderAsync(Long id) {
        return CompletableFuture.supplyAsync(() ->
            restTemplate.getForObject("http://inventory-service/stock/" + id, Order.class));
    }

    public CompletableFuture<Order> timeoutFallback(Long id, TimeoutException e) {
        log.warn("查询超时, id={}", id);
        return CompletableFuture.completedFuture(new Order(id, "超时降级"));
    }
}

8.3 注意:RestTemplate 不支持 cancel

java
// ❌ RestTemplate 是同步阻塞的,cancel-running-future: true 也无法真正中断
// 线程会继续执行,只是结果被丢弃了

// ✅ WebClient 支持响应式取消
@TimeLimiter(name = "orderService")
public CompletableFuture<Order> getOrderAsync(Long id) {
    return webClient.get()
        .uri("http://inventory-service/stock/{id}", id)
        .retrieve()
        .bodyToMono(Order.class)
        .toFuture();  // 真正可以被取消
}

九、Cache(缓存)模块

9.1 配置

yaml
resilience4j:
  cache:
    configs:
      default:
        ttl: 60s                          # 缓存过期时间
        max-size: 100                      # 缓存最大条目数
    instances:
      orderCache:
        base-config: default
        ttl: 30s
        max-size: 1000

9.2 注解式使用

java
@Service
public class OrderService {

    @Cache(name = "orderCache")
    public Order getOrder(Long id) {
        // 如果缓存命中,直接返回缓存结果,不执行方法体
        // 如果缓存未命中,执行方法体,结果存入缓存
        return restTemplate.getForObject("http://order-service/order/" + id, Order.class);
    }
}

9.3 编程式使用

java
@Service
public class OrderService {

    private final Cache<String, Order> cache;

    public OrderService(CacheRegistry registry) {
        this.cache = registry.cache("orderCache");
    }

    public Order getOrder(Long id) {
        String key = "order:" + id;

        return cache.computeIfAbsent(key, k -> {
            // 缓存未命中,执行远程调用
            return restTemplate.getForObject("http://order-service/order/" + id, Order.class);
        });
    }
}

十、多模块组合实战

10.1 组合注解

java
@RestController
public class OrderController {

    @GetMapping("/order/{id}")
    @CircuitBreaker(name = "orderService", fallbackMethod = "cbFallback")
    @RateLimiter(name = "orderService", fallbackMethod = "rlFallback")
    @Retry(name = "orderService", fallbackMethod = "retryFallback")
    @Bulkhead(name = "orderService", fallbackMethod = "bhFallback")
    public Order getOrder(@PathVariable Long id) {
        return restTemplate.getForObject(
            "http://order-service/order/" + id, Order.class);
    }

    // 每个 fallback 独立处理不同异常
    public Order cbFallback(Long id, CallNotPermittedException e) {
        log.warn("熔断降级, id={}", id);
        return Order.fallback(id);
    }

    public Order rlFallback(Long id, RequestNotPermitted e) {
        log.warn("限流降级, id={}", id);
        return Order.fallback(id);
    }

    public Order retryFallback(Long id, Exception e) {
        log.error("重试耗尽, id={}", id);
        return Order.fallback(id);
    }

    public Order bhFallback(Long id, BulkheadFullException e) {
        log.warn("线程池满, id={}", id);
        return Order.fallback(id);
    }
}

10.2 执行顺序(重要!)

请求进入


Bulkhead         ← ① 先检查是否有可用线程/信号量
  │                 (没有 → BulkheadFullException)

RateLimiter      ← ② 检查令牌桶是否有令牌
  │                 (没有 → RequestNotPermitted)

TimeLimiter      ← ③ 设置超时计时器
  │                 (超时 → TimeoutException)

Retry            ← ④ 执行业务逻辑,失败则重试
  │                 (重试耗尽 → 原始异常)

CircuitBreaker   ← ⑤ 统计成功/失败,判断是否熔断
  │                 (已熔断 → CallNotPermittedException)

业务逻辑

理解:Bulkhead 在最外层,CircuitBreaker 在最内层——先保证有资源,再检查是否被限流,然后执行(带重试),最后统计结果更新熔断器。

10.3 编程式组合

java
@Service
public class OrderService {

    private final CircuitBreaker circuitBreaker;
    private final RateLimiter rateLimiter;
    private final Retry retry;
    private final Bulkhead bulkhead;

    public OrderService(Registry registry) {
        this.circuitBreaker = registry.circuitBreaker("orderService");
        this.rateLimiter = registry.rateLimiter("orderService");
        this.retry = registry.retry("orderService");
        this.bulkhead = registry.bulkhead("orderService");
    }

    public Order getOrder(Long id) {
        // 装饰器嵌套:内层先执行,外层后执行
        // 执行顺序:Bulkhead → RateLimiter → Retry → CircuitBreaker → 业务逻辑
        Supplier<Order> supplier = Decorators.ofSupplier(() ->
            restTemplate.getForObject("http://order-service/order/" + id, Order.class)
        )
        .withCircuitBreaker(circuitBreaker)
        .withRetry(retry)
        .withRateLimiter(rateLimiter)
        .withBulkhead(bulkhead)
        .decorate();

        try {
            return supplier.get();
        } catch (Exception e) {
            return Order.fallback(id);
        }
    }
}

十一、事件监听与监控

11.1 全局事件监听

java
@Configuration
public class Resilience4jEventConfig {

    @PostConstruct
    public void registerGlobalEvents(CircuitBreakerRegistry cbRegistry,
                                      RateLimiterRegistry rlRegistry,
                                      RetryRegistry retryRegistry,
                                      BulkheadRegistry bhRegistry) {

        // 熔断器事件
        cbRegistry.getAllCircuitBreakers().forEach(cb ->
            cb.getEventPublisher()
                .onStateTransition(event ->
                    log.warn("熔断器 [{}] {} → {}",
                        cb.getName(),
                        event.getStateTransition().getFromState(),
                        event.getStateTransition().getToState()))
                .onCallNotPermitted(event ->
                    log.warn("熔断器 [{}] 拒绝请求", cb.getName()))
                .onError(event ->
                    log.error("熔断器 [{}] 请求失败, RT={}ms",
                        cb.getName(), event.getElapsedDuration().toMillis()))
        );

        // 限流器事件
        rlRegistry.getAllRateLimiters().forEach(rl ->
            rl.getEventPublisher()
                .onFailure(event ->
                    log.warn("限流器 [{}] 拒绝请求", rl.getName()))
        );

        // 重试器事件
        retryRegistry.getAllRetries().forEach(rt ->
            rt.getEventPublisher()
                .onRetry(event ->
                    log.warn("重试器 [{}] 第 {} 次重试",
                        rt.getName(), event.getNumberOfRetryAttempts()))
                .onError(event ->
                    log.error("重试器 [{}] 重试耗尽", rt.getName()))
        );

        // 隔离器事件
        bhRegistry.getAllBulkheads().forEach(bh ->
            bh.getEventPublisher()
                .onCallRejected(event ->
                    log.warn("隔离器 [{}] 拒绝请求,并发已满", bh.getName()))
        );
    }
}

11.2 Actuator 监控端点

yaml
# 暴露 Actuator 端点
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,circuitbreakers,ratelimiters,retries,bulkheads
  health:
    circuitbreakers:
      enabled: true
    ratelimiters:
      enabled: true

访问端点查看状态:

GET /actuator/circuitbreakers        → 所有熔断器状态
GET /actuator/circuitbreakers/orderService → 单个熔断器详情
GET /actuator/ratelimiters           → 所有限流器状态
GET /actuator/retries                → 所有重试器状态
GET /actuator/health                 → 健康检查(含熔断器状态)

返回示例:

json
{
  "circuitBreakers": {
    "orderService": {
      "failureRate": "25.0%",
      "failureRateThreshold": "50.0%",
      "state": "CLOSED",
      "bufferedCalls": 10,
      "failedCalls": 2,
      "slowCalls": 1,
      "notPermittedCalls": 0
    }
  }
}

十二、与 Feign 集成

yaml
# 开启 Feign 的 Resilience4j 支持
spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true

resilience4j:
  circuitbreaker:
    instances:
      inventory-service:
        sliding-window-size: 10
        failure-rate-threshold: 50
        wait-duration-in-open-state: 5s
  timelimiter:
    instances:
      inventory-service:
        timeout-duration: 3s
java
@FeignClient(name = "inventory-service", url = "http://localhost:8081")
public interface InventoryClient {

    @GetMapping("/stock/{productId}")
    @CircuitBreaker(name = "inventory-service", fallbackMethod = "inventoryFallback")
    @TimeLimiter(name = "inventory-service")
    CompletableFuture<Inventory> getStock(@PathVariable Long productId);

    default CompletableFuture<Inventory> inventoryFallback(Long productId, Exception e) {
        return CompletableFuture.completedFuture(new Inventory(productId, 0));
    }
}

十三、动态配置

java
@Configuration
public class DynamicConfig {

    @Autowired
    private CircuitBreakerRegistry registry;

    // 运行时动态修改熔断器配置
    public void updateCircuitBreaker(String name, float failureRate, int waitSeconds) {
        CircuitBreaker cb = registry.circuitBreaker(name);

        CircuitBreakerConfig newConfig = CircuitBreakerConfig.from(cb.getCircuitBreakerConfig())
            .failureRateThreshold(failureRate)
            .waitDurationInOpenState(Duration.ofSeconds(waitSeconds))
            .build();

        // 注意:dynamically configuring requires recreating the circuit breaker
        // 更推荐使用 Nacos/Apollo 配置中心来动态刷新
    }
}
yaml
# 通过 Spring Cloud Config 或 Nacos 动态刷新
spring:
  cloud:
    refresh:
      extra-refreshable: javax.sql.DataSource

十四、对比选型

维度Resilience4jSentinelHystrix
生态Spring Cloud 官方推荐Spring Cloud AlibabaNetflix(停维)
模块化极高(6 个独立模块,按需引入)一体化一体化
熔断失败率/慢调用率/失败次数失败率/慢调用率/异常数仅失败率
限流令牌桶QPS/线程/关联/热点
重试固定/指数退避/随机无(需配合其他组件)
隔离Semaphore + ThreadPool信号量线程池 + 信号量
控制台无(需配合 Actuator)功能完善的 Dashboard简陋 Dashboard
动态规则支持运行时修改控制台秒级推送有限支持
规则持久化无(需配合配置中心)Nacos/Apollo/ZK
函数式编程原生支持注解为主注解为主
适用场景纯 Spring Cloud 生态Spring Cloud Alibaba 生态老项目维护

选型建议:

纯 Spring Cloud 官方生态 → Resilience4j
  ├── 轻量、模块化、函数式编程
  ├── 6 大模块独立使用,按需引入
  └── 适合对控制台需求不强的团队

Spring Cloud Alibaba 生态 → Sentinel
  ├── 功能更丰富,有 Dashboard
  ├── 规则动态推送,秒级生效
  └── 适合需要可视化管理平台的团队

老项目维护 → Hystrix
  └── 不推荐新项目使用

十五、面试要点

Q1:Resilience4j 有哪些核心模块?各自解决什么问题?

模块解决的问题
CircuitBreaker下游故障,快速失败,防止雪崩
RateLimiter请求过快,超过下游承载能力
Retry网络抖动导致临时失败
Bulkhead慢调用耗尽线程资源,影响其他调用
TimeLimiter下游响应过慢,设定超时
Cache重复查询,减少远程调用

Q2:熔断器三种状态及转换条件?

  CLOSED(正常)→ 失败率/慢调用率超过阈值 → OPEN(熔断,拒绝所有请求)→ 等待 wait-duration-in-open-state 到期 → HALF_OPEN(半开,放行少量试探)→ 试探成功 → CLOSED,试探失败 → OPEN

Q3:Bulkhead 的 Semaphore 和 ThreadPool 区别?

维度SemaphoreThreadPool
实现信号量计数器独立线程池
线程切换
ThreadLocal 传递自动需手动
隔离程度轻量完全隔离
适用场景快调用(< 100ms)慢调用(> 1s)

Q4:Retry 和 CircuitBreaker 的执行顺序?为什么?

  Retry 在前,CircuitBreaker 在后。重试多次失败后,CircuitBreaker 记录失败并可能触发熔断。如果先执行 CircuitBreaker,熔断打开后 Retry 就不会执行,失去了"试探恢复"的机会。

Q5:六模块组合的执行顺序?

  Bulkhead → RateLimiter → TimeLimiter → Retry → CircuitBreaker → 业务逻辑。先保资源,再限流,再超时,再重试,最后统计熔断。

Q6:Resilience4j 和 Sentinel 怎么选?

  • 选 Resilience4j:纯 Spring Cloud 生态、轻量、按需引入、函数式编程
  • 选 Sentinel:需要控制台可视化、规则动态推送、功能更丰富、Alibaba 生态

Q7:滑动窗口类型 COUNT_BASED 和 TIME_BASED 的区别?

  • COUNT_BASED:统计最近 N 个请求,不管时间跨度。适合流量均匀的场景。
  • TIME_BASED:统计最近 N 秒内的请求。适合流量波动大的场景,时间窗口更精确。