本文目录导读:

在Java中实现接口重试有多种方式,从简单的手动重试到使用成熟的第三方库,以下是几种常见实现方案:
简单循环重试(手动实现)
public class SimpleRetryDemo {
public static void main(String[] args) {
SimpleRetryDemo demo = new SimpleRetryDemo();
String result = demo.retry(() -> {
// 模拟远程调用
return demo.callExternalService();
}, 3, 1000);
System.out.println("最终结果: " + result);
}
/**
* 通用重试方法
* @param callable 要执行的操作
* @param maxRetries 最大重试次数
* @param retryDelay 重试间隔(毫秒)
*/
public <T> T retry(Callable<T> callable, int maxRetries, long retryDelay) {
int retries = 0;
while (true) {
try {
return callable.call();
} catch (Exception e) {
retries++;
if (retries >= maxRetries) {
throw new RuntimeException("重试次数耗尽", e);
}
System.out.println("第" + retries + "次重试...");
try {
Thread.sleep(retryDelay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie);
}
}
}
}
// 模拟外部服务调用
private String callExternalService() {
if (Math.random() > 0.3) {
throw new RuntimeException("服务调用失败");
}
return "成功响应";
}
}
使用Spring Retry
// pom.xml 添加依赖
/*
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.5</version>
</dependency>
*/
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 配置重试策略
@Retryable(
value = {RemoteException.class, TimeoutException.class}, // 指定重试的异常类型
maxAttempts = 3, // 最大重试次数
backoff = @Backoff(delay = 1000, multiplier = 2) // 退避策略
)
public String createOrder(Order order) {
// 调用远程服务
return remoteService.createOrder(order);
}
// 所有重试失败后的降级方法
@Recover
public String recover(RemoteException e, Order order) {
// 日志记录
System.out.println("所有重试失败,执行降级逻辑");
return "降级结果";
}
}
使用Guava Retrying
// pom.xml 添加依赖
/*
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
*/
import com.github.rholder.retry.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class GuavaRetryDemo {
public static void main(String[] args) {
GuavaRetryDemo demo = new GuavaRetryDemo();
demo.retryWithGuava();
}
public void retryWithGuava() {
Callable<String> callable = () -> {
// 调用外部接口
if (Math.random() > 0.5) {
throw new RuntimeException("接口调用失败");
}
return "成功";
};
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfExceptionOfType(RuntimeException.class) // 仅重试指定异常
.retryIfResult(result -> result.equals("失败")) // 根据结果重试
.withWaitStrategy(WaitStrategies.exponentialWait(100, 5000, TimeUnit.MILLISECONDS)) // 指数退避
.withStopStrategy(StopStrategies.stopAfterAttempt(5)) // 最多重试5次
.withRetryListener(new RetryListener() { // 重试监听器
@Override
public <V> void onRetry(Attempt<V> attempt) {
System.out.println("第" + attempt.getAttemptNumber() + "次尝试");
if (attempt.hasException()) {
System.out.println("异常: " + attempt.getExceptionCause().getMessage());
}
}
})
.build();
try {
String result = retryer.call(callable);
System.out.println("最终结果: " + result);
} catch (Exception e) {
System.out.println("重试全部失败: " + e.getMessage());
}
}
}
使用Apache HttpClient的重试机制
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
public class HttpClientRetryDemo {
public static void main(String[] args) {
// 自定义重试处理器
DefaultHttpRequestRetryHandler retryHandler =
new DefaultHttpRequestRetryHandler(3, true) { // 最多重试3次
@Override
public boolean retryRequest(IOException exception,
int executionCount,
HttpContext context) {
// 自定义重试逻辑
if (exception instanceof ConnectTimeoutException) {
// 连接超时重试
return executionCount < 5;
}
if (exception instanceof SocketTimeoutException) {
// 读取超时不重试
return false;
}
return super.retryRequest(exception, executionCount, context);
}
};
CloseableHttpClient httpClient = HttpClients.custom()
.setRetryHandler(retryHandler)
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(5000)
.setSocketTimeout(5000)
.build())
.build();
// 使用httpClient发送请求...
}
}
使用线程池的异步重试
import java.util.concurrent.*;
import java.util.function.Supplier;
public class AsyncRetryDemo {
private final ExecutorService executor = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
AsyncRetryDemo demo = new AsyncRetryDemo();
CompletableFuture<String> future = demo.asyncRetry(
() -> "result", // 业务逻辑
3,
1000
);
future.thenAccept(System.out::println)
.exceptionally(e -> {
System.out.println("最终失败: " + e.getMessage());
return null;
});
// 等待异步任务完成
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public <T> CompletableFuture<T> asyncRetry(
Supplier<T> supplier,
int maxRetries,
long retryDelay) {
CompletableFuture<T> future = new CompletableFuture<>();
executor.submit(() -> {
int retries = 0;
while (retries < maxRetries) {
try {
T result = supplier.get();
future.complete(result);
return;
} catch (Exception e) {
retries++;
System.out.println("第" + retries + "次重试");
if (retries >= maxRetries) {
future.completeExceptionally(e);
return;
}
try {
TimeUnit.MILLISECONDS.sleep(retryDelay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
future.completeExceptionally(ie);
return;
}
}
}
});
return future;
}
public void shutdown() {
executor.shutdown();
}
}
自定义注解方式(AOP实现)
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retryable {
int maxRetries() default 3;
long retryDelay() default 1000;
Class<? extends Throwable>[] value() default {};
}
// 使用AOP实现重试逻辑
@Aspect
@Component
public class RetryAspect {
@Around("@annotation(retryable)")
public Object retry(ProceedingJoinPoint joinPoint, Retryable retryable) throws Throwable {
int maxRetries = retryable.maxRetries();
long retryDelay = retryable.retryDelay();
Class<? extends Throwable>[] retryExceptions = retryable.value();
int attempts = 0;
while (true) {
try {
return joinPoint.proceed();
} catch (Throwable e) {
attempts++;
if (attempts >= maxRetries) {
throw e; // 重试次数耗尽,抛出异常
}
// 检查是否应该重试
if (retryExceptions.length > 0 && !shouldRetry(e, retryExceptions)) {
throw e;
}
System.out.println("第" + attempts + "次重试...");
Thread.sleep(retryDelay);
}
}
}
private boolean shouldRetry(Throwable e, Class<? extends Throwable>[] retryExceptions) {
for (Class<? extends Throwable> clazz : retryExceptions) {
if (clazz.isInstance(e)) {
return true;
}
}
return false;
}
}
最佳实践建议
-
选择合适的重试策略:
- 网络相关的错误(超时、连接失败)适合重试
- 业务逻辑错误(参数错误、权限不足)不应重试
- 幂等性操作适合重试
-
退避策略:
- 固定延迟:适用于简单的重试场景
- 指数退避:避免雪崩效应
- 随机延迟:防止惊群效应
-
监控和日志:
- 记录每次重试的详细信息
- 设置重试次数的监控指标
- 超过阈值时发送告警
-
重试次数限制:
- 建议3-5次,避免过多重试
- 考虑业务SLA要求
选择哪种方案取决于项目的具体需求和技术栈,对于简单的项目,使用手动重试就足够了;对于复杂的微服务架构,推荐使用Spring Retry或Guava Retrying等成熟的库。