OkHttpClient 遇到Socket closed 的问题

文章描述了遇到的接口调用socket超时问题,通过分析确定可能是OkHttpClient配置问题,作者提供了修改连接池、超时时间和重试策略的解决方案,虽然问题缓解但仍存在偶然超时,建议前端处理超时错误以改善用户体验。
摘要由CSDN通过智能技术生成

一、现象

当调用某一个接口时,偶尔会报错,报socket 超时关闭的问题。

2024-02-21T14:54:59.308390353+08:00 Caused by: java.net.SocketException: Socket closed
2024-02-21T14:54:59.308397453+08:00 at java.net.SocketInputStream.read(SocketInputStream.java:204)
2024-02-21T14:54:59.308403793+08:00 at java.net.SocketInputStream.read(SocketInputStream.java:141)
2024-02-21T14:54:59.308409063+08:00 at sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:464)
2024-02-21T14:54:59.308416513+08:00 at sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:68)
2024-02-21T14:54:59.308421013+08:00 at sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1341)
2024-02-21T14:54:59.308428353+08:00 at sun.security.ssl.SSLSocketImpl.access$300(SSLSocketImpl.java:73)

二、原因分析

由于不是经常报错。然后以为是负载均衡的问题,通过ping 域名,发现ip是同一个。

感觉有可能是OkHttpClient的问题

然后修改了okHttpClient的配置.添加了连接池,以及设置读取超时时间,写入超时时间,还有设置了连接的超时时间。最后为了降低发生的概率。设置重试策略。

核心代码如下:

@Slf4j
public class OkHttpClientUtils {

    private OkHttpClientUtils() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 获取<code>OkHttpClient</code>单例
     *
     * @return <code>OkHttpClient</code>单例
     */
    public static OkHttpClient getInstance() {
        return OkHttpClientHolder.INSTANCE;
    }

    private static class OkHttpClientHolder {
        private static final OkHttpClient INSTANCE = new OkHttpClient
                .Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
//                自定义连接池最大空闲连接数和等待时间大小,否则默认最大5个空闲连接
                .connectionPool(new ConnectionPool(32, 5, TimeUnit.MINUTES))
                .addInterceptor(new OkHttpRetryInterceptor(3))
                .retryOnConnectionFailure(false)
                .build();
    }

    public static <T> T executePostObject(String reqUrl, Object reqData, Map<String, String> header, TypeReference<T> tTypeReference) throws OkHttpHelperException {
        String result = executePost(reqUrl, reqData, header);
        return JSONObject.parseObject(result, tTypeReference, new Feature[0]);
    }

    public static String executePost(String reqUrl, Object reqData, Map<String, String> reqHeaders) throws OkHttpHelperException {
        OkHttpClient okHttpClient = getInstance();
        if (okHttpClient != null && checkUrl(reqUrl)) {
            okhttp3.Request.Builder reqBuilder = new okhttp3.Request.Builder();
            RequestBody requestBody = null;
            if (checkHeaders(reqHeaders)) {
                reqBuilder.headers(transferToHeaders(reqHeaders));
            }

            if (reqData != null) {
                requestBody = transferToRequestBody(reqData);
                reqBuilder.post(requestBody);
            }

            Request request = reqBuilder.url(reqUrl).build();
            log.info("excutePost(...) request-url: {} , request-body: {}", request.url(), requestBody.toString());
            Call call = okHttpClient.newCall(request);
            try {
                Response response = call.execute();
                String result = response.body().string();
                log.info("excutePost(...) response-result: {}", result);
                return result;
            } catch (Exception var10) {
                log.error("excutePost(...) failed Caused by IOException :{}", var10);
                throw new OkHttpHelperException("failed to excute GET from " + request.url() + " Caused by " + var10.getMessage());
            }
        } else {
            log.error("excutePost(...) failed Caused by invalid url :{}", reqUrl);
            throw new OkHttpHelperException("failed to excute POST Caused by nullable okHttpClient OR invalid url :{}" + reqUrl);
        }
    }

    public static String executeGet(String reqUrl, Map<String, String> reqData, Map<String, String> reqHeaders) throws OkHttpHelperException {
        OkHttpClient instance = getInstance();
        if (instance != null && checkUrl(reqUrl)) {
            okhttp3.Request.Builder reqBuilder = new okhttp3.Request.Builder();
            if (checkHeaders(reqHeaders)) {
                reqBuilder.headers(transferToHeaders(reqHeaders));
            }

            if (checkData(reqData)) {
                reqUrl = transferToUrl(reqUrl, reqData);
            }

            Request request = reqBuilder.url(reqUrl).build();
            log.info("excuteGet(...) request: {}", request.url());
            Call call = instance.newCall(request);

            try {
                Response response = call.execute();
                String result = response.body().string();
                log.info("excuteGet(...) response result: {}", result);
                return result;
            } catch (Exception var9) {
                log.error("failed to excute GET from " + request.url());
                throw new OkHttpHelperException("failed to excute GET from " + request.url() + " Caused by " + var9.getMessage());
            }
        } else {
            log.error("excuteGet(...) failed Caused by invalid url :{}", reqUrl);
            throw new OkHttpHelperException("failed to excute GET Caused by nullable okHttpClient OR invalid url :{}" + reqUrl);
        }
    }
    private static RequestBody transferToRequestBody(Object data) {
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JSON.toJSONString(data));
    }

    private static boolean checkUrl(String url) {
        return url != null && !"".equals(url.trim());
    }

    private static boolean checkData(Map<String, String> data) {
        return data != null && !data.isEmpty();
    }

    private static boolean checkHeaders(Map<String, String> headers) {
        return headers != null && !headers.isEmpty();
    }

    private static Headers transferToHeaders(Map<String, String> headersParams) {
        Headers headers = null;
        okhttp3.Headers.Builder headersbuilder = new okhttp3.Headers.Builder();
        if (headersParams != null) {
            Iterator<String> iterator = headersParams.keySet().iterator();
            String key = "";

            while (iterator.hasNext()) {
                key = ((String) iterator.next()).toString();
                headersbuilder.add(key, (String) headersParams.get(key));
            }
        }

        headers = headersbuilder.build();
        return headers;
    }

    private static String transferToUrl(String url, Map<String, String> data) {
        if (checkUrl(url)) {
            return null;
        } else {
            url = url.trim();
            if (!checkData(data)) {
                return url;
            } else {
                StringBuilder strBuilder = new StringBuilder(url);
                strBuilder.append("?");
                Iterator var4 = data.entrySet().iterator();

                while (var4.hasNext()) {
                    Map.Entry<String, String> entry = (Map.Entry) var4.next();
                    strBuilder.append((String) entry.getKey() + "=" + (String) entry.getValue() + "&");
                }

                String str = strBuilder.toString();
                return str.substring(0, str.length() - 1);
            }
        }
    }
}

重新策略拦截器:

@Slf4j
public class OkHttpRetryInterceptor implements Interceptor {

    /**
     * 最大重试次数
     */
    private final int maxRetry;

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

    @Override
    public Response intercept(Chain chain) {
        return retry(chain, maxRetry);
    }

    private Response retry(Chain chain, int retryCet) {
        Request request = chain.request();
        Response response;
        try {
            response = chain.proceed(request);
        } catch (ConnectTimeoutException | ReadTimeoutException e) {
            if (maxRetry > retryCet) {
                return retry(chain, retryCet + 1);
            }
            log.error("==> 请求接口【{}】超过最大重试次数【{}】仍然失败", request.url(), maxRetry, e);
            // interceptor 返回 null 会报 IllegalStateException 异常
            return new Response.Builder().build();
        } catch (Exception e2) {
            log.error("==> 请求接口【{}】异常", request.url(), e2);
            // interceptor 返回 null 会报 IllegalStateException 异常
            return new Response.Builder().build();
        }
        return response;
    }
}

使用该方法:

OkHttpClientUtils.executeGet(url,reqData,header)

OkHttpClientUtils.executePostObject(url,reqBody,header,tTypeReference)

三、结果

进过优化okhttp相关配置后,问题得到缓解。但是还是有可能会发生超时问题。接着通过设置网络超时报错,让前端通过该错误码,进行重新请求页面。这样可以避免用户感知接口问题超时的存在。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值