重试,你真的用对了吗?聊聊 Java 里的几种重试姿势
日常开发中,网络抖动、依赖服务暂时不可用、并发冲突这类“瞬时故障”太常见了。遇到这些情况,最简单的处理就是——再试一次。但“再试一次”写起来容易,写好了却没那么简单:重试多少次?等多久?什么异常才值得重试?重试失败了怎么兜底?
我这几年经手过几个项目,从最早在循环里 sleep,到后来用 Guava Retrying、Spring Retry,再到最近试了一款叫 FastRetry 的轻量库,踩过不少坑,也攒了一些心得。今天就把这些代码和经验拿出来聊聊。
最朴素的方式:自己写循环 + 休眠
很多初学者(包括我当年)会这么干:
public class RetryUtils<R> {
private int retryTimes = 3;
private long retryTimesLong = 500L;
public Optional<R> getResult() {
for (int i = 1; i <= retryTimes; i++) {
try {
return Optional.of(supplier.get());
} catch (Exception e) {
if (i == retryTimes) throw new RuntimeException(e);
Thread.sleep(retryTimesLong);
}
}
return Optional.empty();
}
}
这段代码简单直接,一个 Builder 风格链式调用,用起来还算舒服。但它有一个致命缺陷:对任何异常都无脑重试。如果是 NullPointerException 或 IllegalArgumentException 这种逻辑错误,重试一万次也没用。另外,固定间隔 500ms 重试,在高并发下可能放大故障。
这种写法适合什么场景?内部测试、对可靠性要求不高的快速脚本。线上核心链路千万别这么用。
Guava Retrying:灵活、成熟,但有点重
Google 的 Guava 库附带了一个 guava-retrying 模块(实际是第三方,但常和 Guava 一起出现)。它的 API 设计我很喜欢:用 RetryerBuilder 组装策略,代码可读性很高。
Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
.retryIfExceptionOfType(AccessException.class) // 只重试特定异常
.withWaitStrategy(WaitStrategies.incrementingWait(2, TimeUnit.SECONDS, 2, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(6))
.build();
这里我配置了:遇到 AccessException 才重试,每次重试间隔递增(第一次等2秒,第二次等4秒…),最多重试6次。
Guava Retrying 支持多种退避策略(固定、随机、指数、斐波那契),也支持自定义重试条件(比如判断返回结果是否为空)。它是目前功能最完备的轻量级重试库,适合大部分中大型项目。
但缺点也明显:依赖 guava-retrying 包(约 200KB),而且设计上偏同步阻塞。如果想异步重试,你得自己包一层 CompletableFuture。
Spring Retry:Spring 生态的亲儿子
如果你在用 Spring Boot,Spring Retry 可能是最顺手的方案。它通过注解和模板两种方式工作,AOP 风格对业务代码侵入小。
@Retryable(value = AccessException.class, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
public Integer getCount() throws AccessException {
// 业务逻辑
}
或者用 RetryTemplate 编程式:
RetryTemplate template = RetryTemplate.builder()
.retryOn(AccessException.class)
.fixedBackoff(1000)
.maxAttempts(3)
.build();
template.execute(context -> riskyMethod(), context -> fallbackValue);
Spring Retry 最大的优点是跟 Spring 生态无缝集成,支持 @Recover 注解做降级。但它的设计有些臃肿,而且默认的 RetryTemplate 是线程不安全的(虽然官方建议每次使用新建实例或单例模式包装)。另外,它的重试策略和退避策略实现比较死板,不如 Guava 灵活。
FastRetry:小众但有点意思
代码里还看到一个 FastRetryTest,用的是 com.burukeyou.retry 这个库(老实说之前我没见过)。简单看了一下,它主打异步非阻塞,RetryQueue 提交任务返回 CompletableFuture,内部用线程池调度重试。
RetryTask<String> task = new RetryTask<String>() {
int result = 0;
@Override
public long waitRetryTime() { return 2000; }
@Override
public boolean retry() { return ++result < 5; }
@Override
public String getResult() { return result + ""; }
};
CompletableFuture<String> future = queue.submit(task);
这个库的设计很适合异步高并发场景,比如调用多个外部服务,各自独立重试。但文档太少,社区几乎没人用,生产环境我不敢用。
小结:怎么选?
- 简单工具类、脚本 → 自己写循环
sleep就够了。 - 中大型项目,对重试策略要求高 → Guava Retrying 首选。
- Spring Boot 项目,已经用了 Spring Retry → 继续用它,注意线程安全。
- 高并发异步场景 → 自己基于
CompletableFuture和线程池封装,或者考虑 Resilience4j(比 FastRetry 靠谱)。

1万+

被折叠的 条评论
为什么被折叠?



