Appearance
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?
| 维度 | Hystrix | Resilience4j |
|---|---|---|
| 依赖 | 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: 3s4.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 # 可以等 100ms5.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.TimeoutException6.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 # 等 100msjava
@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: truejava
@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: 10009.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: 3sjava
@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十四、对比选型
| 维度 | Resilience4j | Sentinel | Hystrix |
|---|---|---|---|
| 生态 | Spring Cloud 官方推荐 | Spring Cloud Alibaba | Netflix(停维) |
| 模块化 | 极高(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 区别?
| 维度 | Semaphore | ThreadPool |
|---|---|---|
| 实现 | 信号量计数器 | 独立线程池 |
| 线程切换 | 无 | 有 |
| 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 秒内的请求。适合流量波动大的场景,时间窗口更精确。
