HttpClient默认重试策略不处理SocketTimeoutException

https://blog.csdn.net/weixin_34288121/article/details/91944946?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-91944946-blog-103029626.t5_download_comparev1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-91944946-blog-103029626.t5_download_comparev1&utm_relevant_index=1

https://blog.csdn.net/u010800970/article/details/79996698?spm=1001.2101.3001.6650.10&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-10-79996698-blog-123966252.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-10-79996698-blog-123966252.pc_relevant_default&utm_relevant_index=12

背景

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

结论先行

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

问题分析

详细场景

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

 
  1. //http设置,retryHandler采用组件提供默认的重试策略DefaultHttpRequestRetryHandler,重试次数为3

  2. return HttpClients.custom()

  3. .setConnectionManager(****)

  4. .setDefaultRequestConfig(****)

  5. .setRetryHandler(new DefaultHttpRequestRetryHandler())

  6. .build();

  7. //请求发起主要代码

  8. HttpPost httpPost = new HttpPost(url);

  9. httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, UTF_8));

  10. CloseableHttpResponse response = httpClient.execute(request);

  11. HttpEntity entity = response.getEntity();

  12. ……其他关闭资源代码

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

 
  1. Caused by: org.apache.http.conn.ConnectTimeoutException: Connect to www.XXX.com:443 [www.XXX.com/XX.1.XX.12] failed: connect timed out

  2. at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:150)

  3. at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)

  4. at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)

  5. at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)

  6. at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)

  7. at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)

  8. at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)

  9. at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)

  10. at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)

  11. at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)

  12. at com.sankuai.meituan.resv.platform.utils.HttpUtil.getResult(HttpUtil.java:124)

  13. ... 36 more

  14. Caused by: java.net.SocketTimeoutException: connect timed out

  15. at java.net.PlainSocketImpl.socketConnect(Native Method)

  16. at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)

  17. at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)

  18. at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)

  19. at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)

  20. at java.net.Socket.connect(Socket.java:589)

  21. at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:337)

  22. at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)

  23. ... 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调用超时。 结果的确没有重试,异常堆栈如下,

 
  1. Caused by: java.net.SocketTimeoutException: Read timed out

  2. at java.net.SocketInputStream.socketRead0(Native Method)

  3. at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)

  4. at java.net.SocketInputStream.read(SocketInputStream.java:171)

  5. at java.net.SocketInputStream.read(SocketInputStream.java:141)

  6. at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)

  7. at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)

  8. at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:282)

  9. at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:140)

  10. at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)

  11. at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)

  12. at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)

  13. at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)

  14. at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)

  15. at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)

  16. at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271)

  17. at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)

  18. at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)

  19. at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)

  20. at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)

  21. at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)

  22. at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)

解决方案

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

 
  1. private class MyHttpRequestRetryHandler implements HttpRequestRetryHandler{

  2. @Override

  3. public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {

  4. if (executionCount > retryTimes){

  5. return false;

  6. }

  7. if (exception instanceof InterruptedIOException

  8. || exception instanceof NoHttpResponseException) {

  9. // Timeout or 服务端断开连接

  10. return true;

  11. }

  12. // Unknown host

  13. if (exception instanceof UnknownHostException) {

  14. return false;

  15. }

  16. // SSL handshake exception

  17. if (exception instanceof SSLException) {

  18. return false;

  19. }

  20. final HttpClientContext clientContext = HttpClientContext.adapt(context);

  21. final HttpRequest request = clientContext.getRequest();

  22. boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);

  23. if (idempotent) {

  24. // Retry if the request is considered idempotent

  25. return true;

  26. }

  27. return false;

  28. }

  29. }

几个超时时间设置参数

 
  1. /** 从连接池获取连接超时时间 **/

  2. private int connectionRequestTimeout = 500;

  3. /** 连接一个url的连接等待时间,建立socket连接时间 **/

  4. private int connectTimeout = 3000;

  5. /** 传输数据等待返回的超时时间 **/

  6. private int socketTimeout = 3000;

反思总结

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值