以下是在弱网环境下实现OkHttp智能重试拦截器的Java代码实现,包含详细注释:
import okhttp3.*;
import okio.Buffer;
import okio.BufferedSink;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLHandshakeException;
public class SmartRetryInterceptor implements Interceptor {
// 最大重试次数
private final int maxRetries;
// 基础等待时间(毫秒)
private final long baseDelayMs;
// 是否启用抖动避免惊群
private final boolean enableJitter;
public SmartRetryInterceptor(int maxRetries, long baseDelayMs) {
this(maxRetries, baseDelayMs, true);
}
public SmartRetryInterceptor(int maxRetries, long baseDelayMs, boolean enableJitter) {
this.maxRetries = maxRetries;
this.baseDelayMs = baseDelayMs;
this.enableJitter = enableJitter;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
RequestBody originalBody = originalRequest.body();
Buffer requestBodyBuffer = null;
// 缓存请求体(用于非GET请求的body复用)
if (originalBody != null && isRetryableMethod(originalRequest)) {
requestBodyBuffer = new Buffer();
originalBody.writeTo(requestBodyBuffer);
}
int retryCount = 0;
IOException lastException = null;
Response response = null;
while (retryCount <= maxRetries) {
// 构建当前请求(如果是重试则使用缓存的body)
Request currentRequest = buildRetryRequest(originalRequest, requestBodyBuffer, retryCount);
try {
response = chain.proceed(currentRequest);
// 处理响应逻辑
if (response.isSuccessful()) {
return response; // 成功则返回
} else if (shouldRetry(response, retryCount)) {
closeResponse(response);
retryCount++;
waitForRetry(retryCount, response);
} else {
return response; // 不可重试的失败响应
}
} catch (IOException e) {
lastException = e;
if (shouldRetry(e, retryCount)) {
retryCount++;
waitForRetry(retryCount, null);
} else {
break; // 不可重试的异常
}
}
}
// 重试耗尽后抛出异常
if (lastException != null) throw lastException;
if (response != null) return response;
throw new IOException("Unknown error occurred");
}
private Request buildRetryRequest(Request original, Buffer bodyBuffer, int retryCount) throws IOException {
if (retryCount == 0 || bodyBuffer == null) return original;
// 构建可重复读取的RequestBody
RequestBody newBody = new RequestBody() {
@Override
public MediaType contentType() {
return original.body().contentType();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
bodyBuffer.copyTo(sink.buffer(), 0, bodyBuffer.size());
}
};
return original.newBuilder()
.method(original.method(), newBody)
.build();
}
private boolean isRetryableMethod(Request request) {
// 仅允许幂等方法的重试
String method = request.method();
return "GET".equalsIgnoreCase(method)
|| "HEAD".equalsIgnoreCase(method)
|| "PUT".equalsIgnoreCase(method)
|| "DELETE".equalsIgnoreCase(method);
}
private boolean shouldRetry(Response response, int retryCount) {
if (retryCount >= maxRetries) return false;
int code = response.code();
return code == 503 || code == 504; // 可扩展其他状态码
}
private boolean shouldRetry(IOException e, int retryCount) {
if (retryCount >= maxRetries) return false;
return e instanceof SocketTimeoutException // 超时
|| e instanceof ConnectException // 连接失败
|| e instanceof SSLHandshakeException // SSL错误
|| (e.getCause() instanceof SSLHandshakeException);
}
private void waitForRetry(int retryCount, Response response) {
try {
long waitTime = calculateWaitTime(retryCount, response);
TimeUnit.MILLISECONDS.sleep(waitTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", e);
}
}
private long calculateWaitTime(int retryCount, Response response) {
// 优先使用Retry-After头
if (response != null) {
String retryAfter = response.header("Retry-After");
if (retryAfter != null) {
try {
return TimeUnit.SECONDS.toMillis(Long.parseLong(retryAfter));
} catch (NumberFormatException ignored) {
// 忽略格式错误
}
}
}
// 指数退避计算
long waitTime = (long) (baseDelayMs * Math.pow(2, retryCount - 1));
// 添加随机抖动(±25%)
if (enableJitter) {
double jitter = 0.25 * waitTime * (Math.random() * 2 - 1);
waitTime += (long) jitter;
}
return Math.max(waitTime, 0);
}
private void closeResponse(Response response) {
if (response != null && response.body() != null) {
try {
response.close();
} catch (Exception ignored) {
}
}
}
}
使用示例:
// 创建OkHttpClient并配置拦截器
OkHttpClient client = new OkHttpClient.Builder()
.retryOnConnectionFailure(false) // 禁用默认重试
.addInterceptor(new SmartRetryInterceptor(
3, // 最大重试3次
1000, // 基础延迟1秒
true // 启用抖动
))
.build();
// 发送请求示例
Request request = new Request.Builder()
.url("https://api.example.com/data")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
// 处理响应
System.out.println(response.body().string());
} catch (IOException e) {
// 处理最终失败
e.printStackTrace();
}
关键特性说明:
-
智能重试条件:
- 自动重试网络异常:超时、连接失败、SSL错误
- 自动重试特定HTTP状态码:503(服务不可用)、504(网关超时)
- 仅对幂等请求(GET/HEAD/PUT/DELETE)自动重试
-
高级重试策略:
- 指数退避算法:每次重试等待时间翻倍
- 随机抖动:避免客户端集群同时重试
- Retry-After头支持:优先遵循服务器的重试建议
-
内存优化:
- 自动缓存非流式请求体
- 支持大文件上传的流式传输(需自行处理body缓存)
-
安全机制:
- 最大重试次数限制
- 线程中断处理
- 响应资源自动释放
扩展建议:
- 如需支持POST请求重试:
// 修改isRetryableMethod方法,添加业务级幂等性判断
private boolean isRetryableMethod(Request request) {
// 根据业务需求扩展
return true;
}
- 添加自定义重试条件:
// 在shouldRetry方法中添加自定义逻辑
private boolean shouldRetry(Response response, int retryCount) {
// 自定义状态码逻辑
return Arrays.asList(408, 500, 502, 503, 504).contains(response.code());
}
- 动态配置策略:
// 使用建造者模式创建配置对象
public class SmartRetryInterceptor {
private final RetryConfig config;
public SmartRetryInterceptor(RetryConfig config) {
this.config = config;
}
public static class RetryConfig {
private int maxRetries = 3;
private long baseDelayMs = 1000;
private boolean enableJitter = true;
// 其他配置项...
}
}
该实现提供了在弱网环境下提升请求成功率的智能重试机制,可根据具体业务