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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • xshell安装步骤_Xshell6

    xshell安装步骤_Xshell6开发环境部署目的:利用ssh远程登陆服务器(在windows系统下远程连接linux)下载XSHELL7XSHELL7下载网址:https://www.netsarang.com/zh/xshell/点击“下载”点击“免费授权界面”以上是XSHELL7的下载过程然后找到右键“以管理员身份运行”一上来会出现这种错误,先点击“是(Y)”过程中一直点击“下一步”,以及“我同意”类似的,然后选择个安装路径就可以没啥特殊的。到最后一切顺利的话会显示下面这样的界面一般通向成功的道

    2025年10月13日
    3
  • SQL 聚合函数之字符串分组合并[通俗易懂]

    SQL 聚合函数之字符串分组合并[通俗易懂]本文介绍了如何通过SQL函数对字符串进行聚合,也就是将多行字符串合并成单个字符串。包括Oracle(LISTAGG函数)、MySQL(GROUP_CONCAT函数)、SQLServer(STRING_AGG函数)、PostgreSQL(STRING_AGG函数)以及SQLite(GROUP_CONCAT函数)的语法和差异。

    2022年6月21日
    116
  • java实现国密SM4加密「建议收藏」

    java实现国密SM4加密「建议收藏」前言最近世界政治影响,我国也开始要求算法的使用,以避免来自外国的黑客入侵。我们在使用加密算法时,有必要选择使用国密算法进行加密一、国密SM4是什么? 国密即国家密码局认定的国产密码算法。 主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。 SM1为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。 SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA ECC2.

    2022年10月5日
    4
  • 1到n全排列的hash函数(o(n))

    1到n全排列的hash函数(o(n))

    2021年8月11日
    62
  • tkmapper mybatis plus 集成_gitea docker

    tkmapper mybatis plus 集成_gitea docker———————————————————————————————————————————-一、设置TkMapper单独放在一个包:这个包只有TkMapper一个文件importtk.mybatis.mapper.common.M…

    2022年10月7日
    3
  • python类型转换astype时间_python dataframe astype 字段类型转换方法

    python类型转换astype时间_python dataframe astype 字段类型转换方法使用astype实现dataframe字段类型转换#-*-coding:UTF-8-*-importpandasaspddf=pd.DataFrame([{‘col1′:’a’,’col2′:’1′},{‘col1′:’b’,’col2′:’2′}])printdf.dtypesdf[‘col2’]=df[‘col2’].astype(‘int’)print’–…

    2022年6月5日
    47

发表回复

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

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