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


相关推荐

  • 安防摄像头已分别接入乐橙云、萤石云,如何实现私有云平台的统一管理与向上级联?

    安防摄像头已分别接入乐橙云、萤石云,如何实现私有云平台的统一管理与向上级联?一、背景分析科技创新对社会发展的重要性不言而喻。对于安防行业来说,人工智能和5G等新技术的应用,已经成为推动智能安防发展的一块关键踏板。从技术维度来看,人工智能、云计算、大数据、物联网等技术在安防视频监控领域产生了较多的交汇与融合。新技术打破了传统安防产业以硬件为主的局面,带来了软件定义的摄像机、云平台等概念。芯片技术不断进步,AI算力成本有所降低;深度学习算法及相关框架、开源软件的成熟使得AI算法普惠化;GB/T28181、ONVIF等行业标准的成熟,降低了视频图像数据互联互通的难度;H.265/

    2022年6月9日
    87
  • 小旋风asp服务器出错_小旋风服务器用来干什么的

    小旋风asp服务器出错_小旋风服务器用来干什么的 点击是出现下列的对话框:可能是因为因为开着迅雷,因为迅雷是用的80号端口,可以关了迅雷试试,

    2025年8月7日
    4
  • html倒计时免费代码,JS倒计时代码汇总[通俗易懂]

    本文实例总结了常见的JS倒计时代码。分享给大家供大家参考。具体汇总如下:第一种:精确到秒的javascript倒计时代码HTML代码:离2010年还有:startclock()vartimerID=null;vartimerRunning=false;functionshowtime(){Today=newDate();varNowHour=Today.getHours…

    2022年4月6日
    77
  • java版我的世界下载_我的世界java版

    java版我的世界下载_我的世界java版我的世界java版这个所谓的java版可能大家不是很熟悉,java版就是《我的世界》是整个游戏的初始版本,目前Java版本是全平台游戏版本中内容最多,更新速度最快的版本。此外,Java版本拥有大规模的全球玩家社区,得益于它可拓展的特性,拥有百万件玩家创意作品,这些都使得Java版本是核心玩家最喜爱的版本之一。我的世界java版手机版种子:出生点前有丛林神庙的地图种子在这个种子地图上,你会出生在一座…

    2022年7月8日
    37
  • jquery时间轴幻灯展示源代码

    查看效果:http://hovertree.com/texiao/jquery/75/源代码下载:http://hovertree.com/h/bjaf/8jlpc2wu.htm效果图如下:代码如下:

    2021年12月26日
    42
  • 手把手教你在Linux环境下安装Python3「建议收藏」

    在上一篇文章《手把手教你启用Win10的Linux子系统(超详细)》我们已经学了如何在Win10环境下装Linux子系统了,那么这一篇文章我们将学习如何在该Linux系统下安装Python3。首先是按Win+R键调出cmd命令窗口,然后输入输入bash指令进入Ubuntu系统,接着就可以进入正式的安装过程了。第一步、下载Python3输入下载命令:wgethttps://w…

    2022年4月17日
    40

发表回复

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

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