弱网环境下OkHttp拦截器实现智能重试机制

262 阅读3分钟

以下是在弱网环境下实现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();
}

关键特性说明:

  1. 智能重试条件:

    • 自动重试网络异常:超时、连接失败、SSL错误
    • 自动重试特定HTTP状态码:503(服务不可用)、504(网关超时)
    • 仅对幂等请求(GET/HEAD/PUT/DELETE)自动重试
  2. 高级重试策略:

    • 指数退避算法:每次重试等待时间翻倍
    • 随机抖动:避免客户端集群同时重试
    • Retry-After头支持:优先遵循服务器的重试建议
  3. 内存优化:

    • 自动缓存非流式请求体
    • 支持大文件上传的流式传输(需自行处理body缓存)
  4. 安全机制:

    • 最大重试次数限制
    • 线程中断处理
    • 响应资源自动释放

扩展建议:

  1. 如需支持POST请求重试:
// 修改isRetryableMethod方法,添加业务级幂等性判断
private boolean isRetryableMethod(Request request) {
    // 根据业务需求扩展
    return true;
}
  1. 添加自定义重试条件:
// 在shouldRetry方法中添加自定义逻辑
private boolean shouldRetry(Response response, int retryCount) {
    // 自定义状态码逻辑
    return Arrays.asList(408, 500, 502, 503, 504).contains(response.code());
}
  1. 动态配置策略:
// 使用建造者模式创建配置对象
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;
        // 其他配置项...
    }
}

该实现提供了在弱网环境下提升请求成功率的智能重试机制,可根据具体业务