一、现象
当调用某一个接口时,偶尔会报错,报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相关配置后,问题得到缓解。但是还是有可能会发生超时问题。接着通过设置网络超时报错,让前端通过该错误码,进行重新请求页面。这样可以避免用户感知接口问题超时的存在。