基于OkHttp实现HTTP请求重连机制详解

0 次阅读

OkHttp作为Android和Java生态中应用广泛的HTTP客户端,凭借连接池、请求拦截器、缓存机制以及HTTP/2支持等特性,被大量项目用于网络通信。然而,在真实业务环境中,网络请求并不总是一帆风顺,弱网络、服务器短暂不可用、连接超时、移动网络切换等情况都可能导致HTTP请求失败。

为了提升应用稳定性,很多开发者会为OkHttp设计请求重连机制。合理的HTTP请求重连策略能够减少偶发网络异常带来的失败,提高接口调用成功率,同时改善用户体验。本文将详细介绍基于OkHttp实现HTTP请求重连机制的方法、核心原理以及实际开发中的注意事项。

一、为什么需要HTTP请求重连机制

网络环境具有天然的不确定性,即使服务器和客户端代码完全正常,请求依然可能因为以下原因失败:

  • 手机从WiFi切换到移动网络导致连接断开;

  • DNS解析失败;

  • TCP连接建立超时;

  • 服务端短暂重启;

  • 网络丢包导致请求超时;

  • 代理服务器异常;

  • 服务端返回临时性错误。

例如,一个支付查询接口在请求过程中因为网络波动失败,如果客户端直接提示用户失败,会影响业务体验。通过请求重连机制,可以等待短暂时间后自动再次尝试,从而提高请求成功概率。

不过,需要注意的是,重连并不是所有失败请求都应该执行。对于参数错误、权限不足、业务校验失败等明确无法成功的请求,重复发送只会增加服务器压力。

二、OkHttp自带的连接恢复能力

很多开发者误认为OkHttp没有重连功能,实际上OkHttp内部已经提供了一定程度的连接恢复能力。

OkHttp默认支持:

  • 自动恢复部分连接失败;

  • 连接池复用;

  • 路由切换;

  • HTTP代理失败切换;

  • TLS握手异常恢复。

例如:

OkHttpClient client = new OkHttpClient.Builder()
        .retryOnConnectionFailure(true)
        .build();

retryOnConnectionFailure(true)表示开启连接失败自动恢复功能。

但是,这种机制主要针对底层连接问题,并不等同于完整的业务请求重试。

例如:

  • HTTP 500服务器错误不会自动重新请求;

  • HTTP 429限流不会自动等待;

  • 请求超时后的业务重试需要自行实现;

  • POST请求是否重试需要开发者判断。

因此,在实际项目中通常需要基于OkHttp扩展更完善的重连策略。

三、OkHttp实现请求重连的核心思路

一个完整的HTTP请求重连机制通常包含以下几个部分:

1. 设置最大重试次数

无限重试会造成严重问题,因此必须限制次数。

例如:

private static final int MAX_RETRY_COUNT = 3;

表示最多请求3次。

一般建议:

  • 普通查询接口:2~3次;

  • 核心业务接口:根据业务容忍度调整;

  • 上传接口:谨慎重试。

2. 判断是否允许重试

不是所有异常都适合重新请求。

常见可重试情况:

  • IOException;

  • SocketTimeoutException;

  • ConnectException;

  • HTTP 502;

  • HTTP 503;

  • HTTP 504。

不建议重试:

  • HTTP 400;

  • HTTP 401;

  • HTTP 403;

  • 参数校验失败。

判断逻辑示例:

private boolean shouldRetry(Response response) {

    int code = response.code();

    return code == 502
            || code == 503
            || code == 504;
}

四、通过Interceptor实现OkHttp请求重试

OkHttp提供Interceptor机制,可以在请求执行过程中统一处理重试逻辑。

示例:

public class RetryInterceptor implements Interceptor {

    private int maxRetry;

    public RetryInterceptor(int maxRetry) {
        this.maxRetry = maxRetry;
    }


    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();

        Response response = null;

        int retryCount = 0;


        while (retryCount < maxRetry) {

            try {

                response = chain.proceed(request);

                if (response.isSuccessful()) {
                    return response;
                }


                if (!needRetry(response)) {
                    return response;
                }


            } catch (IOException e) {

                if (retryCount >= maxRetry - 1) {
                    throw e;
                }

            }


            retryCount++;

            try {
                Thread.sleep(1000L * retryCount);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }


        return response;
    }


    private boolean needRetry(Response response) {

        int code = response.code();

        return code == 502
                || code == 503
                || code == 504;
    }
}

添加Interceptor:

OkHttpClient client =
        new OkHttpClient.Builder()
        .addInterceptor(new RetryInterceptor(3))
        .build();

这样所有通过该OkHttpClient发出的请求都会经过统一重试处理。

五、使用指数退避策略优化重连

简单固定时间等待虽然容易实现,但效果有限。

例如:

第一次失败等待1秒;
第二次失败等待2秒;
第三次失败等待4秒。

这种方式称为指数退避(Exponential Backoff)。

计算方式:

long delay = (long) Math.pow(2, retryCount) * 1000;

优势包括:

  • 避免大量客户端同时重试;

  • 降低服务器瞬间压力;

  • 给故障恢复留出时间。

在高并发系统中,例如订单系统、消息系统,指数退避几乎是标准设计方案。

六、处理POST请求重试问题

GET请求通常没有副作用,因此失败后重新发送比较安全。

但是POST请求需要特别注意。

例如:

用户提交订单:

第一次请求已经到达服务器;
服务器返回超时;
客户端认为失败重新发送。

可能导致:

  • 创建两个订单;

  • 重复扣款;

  • 数据重复写入。

解决方式主要有两种:

1. 使用幂等设计

服务端提供唯一业务编号:

requestId=202608250001

服务器收到重复请求时,根据requestId判断是否已经处理。

2. 控制请求类型

对于非幂等操作:

  • 不自动重试;

  • 交由业务层确认。

七、结合Call机制实现单个请求重试

除了Interceptor,也可以针对单个请求实现重试。

示例:

public void requestWithRetry(
        OkHttpClient client,
        Request request) throws IOException {


    int retry = 0;

    while (retry < 3) {

        try {

            Response response =
                    client.newCall(request).execute();


            if(response.isSuccessful()){
                return;
            }

        } catch(IOException e){

            if(retry == 2){
                throw e;
            }
        }

        retry++;
    }
}

这种方式适合:

  • 少量特殊接口;

  • 上传任务;

  • 下载任务;

  • 后台同步任务。

如果项目中大量接口需要统一处理,更推荐Interceptor方案。

八、OkHttp异步请求中的重连处理

实际开发中更多使用异步请求:

client.newCall(request)
.enqueue(new Callback() {

@Override
public void onFailure(
        Call call,
        IOException e) {

}


@Override
public void onResponse(
        Call call,
        Response response) {

}

});

异步模式下不能直接使用while循环阻塞线程。

更推荐:

  • 在失败回调中重新创建Call;

  • 配合Handler或协程延迟执行;

  • 使用任务调度框架控制重试。

例如:

private void retryRequest(Call call){

    call.clone().enqueue(callback);

}

clone()可以复制一个新的Call对象,避免重复执行已经使用过的Call。

九、设置合理的超时时间

重连机制必须配合超时策略,否则一次失败可能等待很久。

OkHttp配置:

OkHttpClient client =
        new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)