重试速查
.retryWhen(Retry.backoff(3, Duration.ofMillis(100))
.maxBackoff(Duration.ofSeconds(2))
.jitter(0.5)
.filter(err -> err instanceof WebClientRequestException
|| (err instanceof WebClientResponseException wce
&& wce.getStatusCode().is5xxServerError())))
Retry.backoff(3, ...) 是「重试 3 次」,加上首次调用共 4 次。WebClient 的 4xx 不应该重试——WebClientResponseException 覆盖所有非 2xx 响应,要再按 is5xxServerError() 过滤;连接失败、DNS 失败等则是 WebClientRequestException。
| 参数 | 含义 |
|---|---|
3 | 最多重试次数 |
Duration.ofMillis(100) | 初始间隔 |
maxBackoff | 最大间隔 |
jitter | 抖动比例 |
filter | 只对特定错误重试 |
熔断速查
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(10)
.slidingWindowSize(100)
.build();
CircuitBreaker breaker = CircuitBreaker.of("api", config);
// 使用
mono.transformDeferred(CircuitBreakerOperator.of(breaker));
限流速查
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(1000)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofMillis(10))
.build();
RateLimiter limiter = RateLimiter.of("api", config);
// 使用
mono.transformDeferred(RateLimiterOperator.of(limiter));
降级速查
.onErrorResume(WebClientRequestException.class, err ->
Mono.just(defaultValue))
.onErrorResume(err ->
Mono.just(fallbackValue))
.onErrorReturn("static-fallback");
注意 java.util.concurrent.TimeoutException、Reactor Netty 的 ReadTimeoutException、WebClientRequestException 是不同异常;只写 TimeoutException.class 经常匹配不到真正的超时。先用日志确认异常类型,再写 onErrorResume。
组合顺序
请求
→ 限流(最外层,超量请求不进入重试和熔断统计)
→ 重试(中间层,每次重试都经过熔断器)
→ 熔断(最内层,保护下游并快速失败)
→ 业务逻辑
webClient.get().uri(url)
.retrieve()
.bodyToMono(String.class)
.transformDeferred(CircuitBreakerOperator.of(breaker)) // 1 最内层
.retryWhen(Retry.backoff(2, Duration.ofMillis(100))
.filter(err -> !(err instanceof CallNotPermittedException))) // 2
.transformDeferred(RateLimiterOperator.of(rateLimiter)) // 3 最外层
.onErrorResume(err -> Mono.just(fallback)); // 4
Reactor 里「写在后面的操作符是外层的」,所以最外层限流要写在最后。重试必须排除 CallNotPermittedException,否则熔断器打开后重试还会反复发起注定被拒的调用。
参数对照表
| 场景 | 重试 | 熔断 | 限流 | 降级 |
|---|---|---|---|---|
| 内部服务调用 | 2–3 次 | 50% / 30s | 1000/s | 缓存 |
| 外部第三方 API | 1–2 次 | 50% / 60s | 100/s | 失败 |
| 写操作(关键) | 0–1 次 | 80% / 10s | 100/s | 不降级 |
| 读操作(非关键) | 3 次 | 50% / 30s | 5000/s | 空值 |
小结
弹性模式不是越多越好。每个模式都会增加复杂度和延迟,只在有明确故障场景时使用。重试 + 熔断 + 限流能覆盖大部分同步调用场景,但写操作的重试必须先保证幂等,否则重试只会制造重复数据。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。