HttpClient默认重试策略不处理SocketTimeoutException

HttpClient默认重试策略不处理SocketTimeoutException

大家好,又见面了,我是你们的朋友全栈君。

背景

与公司外服务采用http交互,使用httpClient 4.5.2版本的默认重试策略DefaultHttpRequestRetryHandler,发现对方接口连接超时(3秒)后, 并不会重试 而是直接抛出ConnectTimeoutException,不符合预期。

结论先行

httpClient默认重试策略DefaultHttpRequestRetryHandler针对连接超时和获取数据超时并不会重试,需要自定义重试策略。

问题分析

详细场景

采用HttpClient 4.5.2发起http请求,代码如下,使用方式正常

//http设置,retryHandler采用组件提供默认的重试策略DefaultHttpRequestRetryHandler,重试次数为3
return HttpClients.custom()
                .setConnectionManager(****)
                .setDefaultRequestConfig(****)
                .setRetryHandler(new DefaultHttpRequestRetryHandler())
                .build();

//请求发起主要代码
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, UTF_8));
CloseableHttpResponse response = httpClient.execute(request);
HttpEntity entity = response.getEntity();
……其他关闭资源代码

请求后过了超时时间3s得到ConnectTimeoutException异常,且log中没有重试日志,堆栈如下:

Caused by: org.apache.http.conn.ConnectTimeoutException: Connect to www.XXX.com:443 [www.XXX.com/XX.1.XX.12] failed: connect timed out
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:150)
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
	at com.sankuai.meituan.resv.platform.utils.HttpUtil.getResult(HttpUtil.java:124)
	... 36 more
Caused by: java.net.SocketTimeoutException: connect timed out
	at java.net.PlainSocketImpl.socketConnect(Native Method)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:337)
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)
	... 46 more

原因分析

出现这个问题,只好看默认重试策略的代码了,DefaultHttpRequestRetryHandler实现了HttpRequestRetryHandler,其中有这么一个方法public boolean retryRequest(final IOException exception,final int executionCount,final HttpContext context),判断是否需要重试。

那么我们试图请求facebook(实验机器访问不了)来复现连接超时的情况,并进行断点调试,看这个方法的返回值。抛出的异常符合预期,即为ConnectTimeoutException,断点情况如下: timeout断点

该方法的确返回了false,即不会重试。

代码分析

实例化DefaultHttpRequestRetryHandler时会初始化nonRetriableClasses,结果如上图圈红出所示,然后发现这个继承关系public class ConnectTimeoutException extends InterruptedIOException {
,所以进入if分支返回false。

举一反三读取数据超时

连接超时不会重试,那读取数据超时是否会重试呢,根据继承关系public class SocketTimeoutException extends java.io.InterruptedIOException 判断是不会重试的。我们通过测试来复现下。

复现方式:http请求本地某个服务,设置SocketTimeout为3s,在服务端打上断点,让client调用超时。 结果的确没有重试,异常堆栈如下,

Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
	at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
	at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:282)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:140)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
	at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
	at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
	at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)
	at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
	at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)

解决方案

自定义重试策略,某些异常时让它返回true重试就好。

private class MyHttpRequestRetryHandler implements HttpRequestRetryHandler{

        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (executionCount > retryTimes){
                return false;
            }
            if (exception instanceof InterruptedIOException
                    || exception instanceof NoHttpResponseException) {
                // Timeout or 服务端断开连接
                return true;
            }
            // Unknown host
            if (exception instanceof UnknownHostException) {
                return false;
            }
            // SSL handshake exception
            if (exception instanceof SSLException) {
                return false;
            }

            final HttpClientContext clientContext = HttpClientContext.adapt(context);
            final HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // Retry if the request is considered idempotent
                return true;
            }
            return false;
        }
    }

几个超时时间设置参数

    /** 从连接池获取连接超时时间 **/
    private int connectionRequestTimeout = 500;

    /** 连接一个url的连接等待时间,建立socket连接时间 **/
    private int connectTimeout = 3000;

    /** 传输数据等待返回的超时时间 **/
    private int socketTimeout = 3000;

反思总结

coding时认为默认重试策略遇到连接超时是会重试的,但实际没有进行这种异常case的测试,也没有看默认策略的代码实现,所以线上出现超时并没有重试的情况。

总结 :为异常流程开发的处理方式,测试的时候虽然会麻烦些,但应该覆盖到,保证线上真正出现异常时恢复流程能正常work。

转载于:https://my.oschina.net/hebaodan/blog/1795348

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107579.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 如何卸载赛门铁克symantec,ivanti[通俗易懂]

    如何卸载赛门铁克symantec,ivanti[通俗易懂]1、会安装这个软件的公司大概率不会改密码,默认卸载密码就是:symantec2、如果不幸改了密码,用下面这个软件可以卸载大部分功能链接:https://pan.baidu.com/s/14evQQ1sHh2FCmYjtBBmMqw提取码:luna

    2022年5月2日
    281
  • mysql服务性能优化—my.cnf配置说明详解

    mysql服务性能优化—my.cnf配置说明详解

    2021年6月5日
    89
  • 计算机 修改 虚拟ip,电脑虚拟ip地址怎么弄?「建议收藏」

    计算机 修改 虚拟ip,电脑虚拟ip地址怎么弄?「建议收藏」原标题:电脑虚拟ip地址怎么弄?在需要建立局域网和连接多台计算机时。我将重新设置电脑的IP地址,这样电脑就可以连上网了。因此,如何设置电脑的IP地址呢?以下是根据电脑原来的IP地址设置IP地址的共享方法。设置步骤:1、首先,右击桌面上的网络邻居,并在打开的菜单中选择该属性。2、在打开的“网络连接”属性菜单中找到本地连接,单击“本地连接”,然后点击“查看此连接状态”,即可打开本地连接。单击“支持”标…

    2022年10月12日
    3
  • flex vue 垂直居中居上_flex 垂直居中、两列对齐、自适应宽[通俗易懂]

    flex vue 垂直居中居上_flex 垂直居中、两列对齐、自适应宽[通俗易懂]####flex垂直居中#parent{display:flex;width:300px;height:300px;outline:solid1px;justify-content:center;align-content:center;//主轴居中对齐align-items:center;//交叉轴的中点对齐}#child{width:100px;height:100px;outlin…

    2022年5月6日
    52
  • pso粒子群优化算法例题_多目标优化算法

    pso粒子群优化算法例题_多目标优化算法粒子群算法属于智慧算法的一类,与该类算法类似的还有蚁群算法,遗传算法等。大家可以将这几种算法进行比较。粒子群优化算法(ParticleSwarmOptimization,PSO)属于进化算法的一种,是通过模拟鸟群捕食行为设计的。从随机解出发,通过迭代寻找最优解,通过适应度来评价解的品质。在这里,我们举一个例子来深入理解一下该算法:假设有一鸟群,在一座岛上某个地方放有食物,但是鸟群并不知道食…

    2022年10月11日
    3
  • docker pull image 打tag

    docker pull image 打tag

    2021年5月14日
    138

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号