HttpClient默认重试策略不处理SocketTimeoutException
2018-04-13 00:00
1206 查看
摘要: # 背景
与公司外服务采用http交互,使用httpClient 4.5.2版本的默认重试策略DefaultHttpRequestRetryHandler,发现对方接口连接超时(3秒)后, **并不会重试** 而是直接抛出```ConnectTimeoutException```,不符合预期。
# 结论先行
httpClient默认重试策略DefaultHttpRequestRetryHandler针对连接超时和获取数据超时并不会重试,需要自定义重试策略。
请求后过了超时时间3s得到ConnectTimeoutException异常,且log中没有重试日志,堆栈如下:
那么我们试图请求facebook(实验机器访问不了)来复现连接超时的情况,并进行断点调试,看这个方法的返回值。抛出的异常符合预期,即为ConnectTimeoutException,断点情况如下:
该方法的确返回了false,即不会重试。
复现方式:http请求本地某个服务,设置SocketTimeout为3s,在服务端打上断点,让client调用超时。
结果的确没有重试,异常堆栈如下,
总结 :为异常流程开发的处理方式,测试的时候虽然会麻烦些,但应该覆盖到,保证线上真正出现异常时恢复流程能正常work。
与公司外服务采用http交互,使用httpClient 4.5.2版本的默认重试策略DefaultHttpRequestRetryHandler,发现对方接口连接超时(3秒)后, **并不会重试** 而是直接抛出```ConnectTimeoutException```,不符合预期。
# 结论先行
httpClient默认重试策略DefaultHttpRequestRetryHandler针对连接超时和获取数据超时并不会重试,需要自定义重试策略。
背景
与公司外服务采用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,断点情况如下:
该方法的确返回了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。
相关文章推荐
- HttpClient java.net.SocketTimeoutException: Read timed out!
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- Exception in thread "main" java.net.SocketTimeoutException: connect timed ou错误处理
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和Browse
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- org.apache.solr.client.solrj.SolrServerException: java.net.SocketTimeoutException: Read timed out
- HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- HttpClient请求时connectionRequestTimeout 、connectionTimeout、socketTimeout三个超时时间的含义
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp
- 稳扎稳打Silverlight(53) - 4.0通信之对WCF NetTcpBinding的支持, 在Socket通信中通过HTTP检索策略文件, HTTP请求中的ClientHttp和BrowserHttp