HttpClient重试策略导致的SocketTimeoutException异常

本文主要是介绍HttpClient重试策略导致的SocketTimeoutException异常,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

有业务部门反馈,在使用SOA框架进行远程调用的时候,出现SocketTimeoutException异常,并且发现是在HTTP status =429的时候才会造成这种情况

背景介绍

  1. 我们的SOA框架有一种场景,为了兼容老的业务能介入服务化治理,使用了一种称为”泛华的方式“,让业务能像调用本地接口一样,调用下游的HTTP服务
  2. 业务服务端:第一次访问接口时候,返回的是200,再次重复调用,会返回HTTP status =429 (具体含义参考HTTP规范)

排查流程

  1. 业务首先演示了使用我们的SOA框架的时候,第一次调用f返回200的时候,是正常的耗时ms级别,第二次时候耗时11S多, 且抛出SocketTimeoutException异常
###|2022-10-28 18:27:33.610|ERROR|-|b431ac67-a4ad-4ff3-834b-34a1f9030338|http-nio-8080-exec-5|SoaRequestAspect--->soa call error 
java.net.SocketTimeoutException: 10000 MILLISECONDSat org.apache.hc.core5.io.SocketTimeoutExceptionFactory.create(SocketTimeoutExceptionFactory.java:50)at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onTimeout(AbstractHttp1StreamDuplexer.java:398)at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.timeout(AbstractHttp1IOEventHandler.java:82)at org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler.timeout(ClientHttp1IOEventHandler.java:39)at org.apache.hc.core5.reactor.InternalDataChannel.onTimeout(InternalDataChannel.java:158)at org.apache.hc.core5.reactor.InternalChannel.checkTimeout(InternalChannel.java:67)at org.apache.hc.core5.reactor.SingleCoreIOReactor.checkTimeout(SingleCoreIOReactor.java:241)at org.apache.hc.core5.reactor.SingleCoreIOReactor.validateActiveChannels(SingleCoreIOReactor.java:168)at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:130)at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:85)at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)at java.lang.Thread.run(Thread.java:748)
###|2022-10-28 18:27:33.615|WARN|-|b431ac67-a4ad-4ff3-834b-34a1f9030338|http-nio-8080-exec-5|BusinessParamException--->业务错误,ret:null, code:svc..null, msg:sever logic err
  1. 使用postman调用的时候,第一次调用(返回200)和第二次调用(返回429)都很快且不会超时

  2. 和业务方一起检查服务端日志,发现不管是SOA代码还是postman调用,均在几ms就返回结果

  3. 在本地使用Wireshark查看TCP连接情况
    在这里插入图片描述

  4. 分析上面TCP连接情况,发现在No.858 行返回429结果后,在No.886 又有一次PUSH请求,查看发现是有一次http请求
    在这里插入图片描述

  5. No.418 第一次发起请求,No.427 返回HTTP status = 200,正常

  6. No.841 第二次发起请求,No.858 返回HTTP status = 429(再次调用是通过重启客户端方式)

  7. No.886 客户端发起重试,查看报文发现是只重发了请求头,请求体Body丢失了

  8. 服务端一直在等待客户端发送请求体,导致超时

  9. 通过上诉日志链接,查看服务端只返回了 1 次HTTP status = 429,No.886 客户端发起重试的没有返回日志

  10. 这个是HttpClient的一个Bug,目前已经在5.1.3版本修复,具体可查看 ISSUES HTTPCLIENT-2194 或者 Version 5.1.3 Release Notes

原因:
HttpClient5 重试时未正常发送请求体,导致的服务端等待超时

附录

  1. 正常的HttpClient重试流程抓包流程
    在这里插入图片描述

  2. HttpClient重试策略代码

客户端使用的重试策略是默认的重试策略,具体在代码 org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy
4.X版本默认重试3次,5.X版本默认重试1次

创建HttpClient


CloseableHttpAsyncClient asyncClient = builder.build();

在build方法中有HttpRequestRetryStrategy的设置,默认是实现是DefaultHttpRequestRetryStrategy

// Add request retry executor, if not disabledif (!automaticRetriesDisabled) {HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;if (retryStrategyCopy == null) {retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;}execChainDefinition.addFirst(new AsyncHttpRequestRetryExec(retryStrategyCopy),ChainElement.RETRY.name());}

查看DefaultHttpRequestRetryStrategy代码可知

在以下两种情况均会重试

  1. 发生除以下的异常:InterruptedIOException.class,UnknownHostException.class,ConnectException.class,ConnectionClosedException.class,NoRouteToHostException.class,SSLException.class
  2. 返回HTTP status=429 或 503 时

1、在5.X版本中,默认重试一次,在4.X版本是重试3次
2、除了以下几种异常的时候发生异常会重试

org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy#retryRequest(org.apache.hc.core5.http.HttpRequest, java.io.IOException, int, org.apache.hc.core5.http.protocol.HttpContext)

InterruptedIOException.class,
UnknownHostException.class,
ConnectException.class,
ConnectionClosedException.class,
NoRouteToHostException.class,
SSLException.class)
3、 Response的http statuis在下面两种情况也会重试

org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy#retryRequest(org.apache.hc.core5.http.HttpResponse, int, org.apache.hc.core5.http.protocol.HttpContext)

HttpStatus.SC_TOO_MANY_REQUESTS,
HttpStatus.SC_SERVICE_UNAVAILABLE)

这篇关于HttpClient重试策略导致的SocketTimeoutException异常的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/289114

相关文章

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

C#文件复制异常:"未能找到文件"的解决方案与预防措施

《C#文件复制异常:未能找到文件的解决方案与预防措施》在C#开发中,文件操作是基础中的基础,但有时最基础的File.Copy()方法也会抛出令人困惑的异常,当targetFilePath设置为D:2... 目录一个看似简单的文件操作问题问题重现与错误分析错误代码示例错误信息根本原因分析全面解决方案1. 确保

Java利用@SneakyThrows注解提升异常处理效率详解

《Java利用@SneakyThrows注解提升异常处理效率详解》这篇文章将深度剖析@SneakyThrows的原理,用法,适用场景以及隐藏的陷阱,看看它如何让Java异常处理效率飙升50%,感兴趣的... 目录前言一、检查型异常的“诅咒”:为什么Java开发者讨厌它1.1 检查型异常的痛点1.2 为什么说

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁

MySQL设置密码复杂度策略的完整步骤(附代码示例)

《MySQL设置密码复杂度策略的完整步骤(附代码示例)》MySQL密码策略还可能包括密码复杂度的检查,如是否要求密码包含大写字母、小写字母、数字和特殊字符等,:本文主要介绍MySQL设置密码复杂度... 目录前言1. 使用 validate_password 插件1.1 启用 validate_passwo

Java异常捕获及处理方式详解

《Java异常捕获及处理方式详解》异常处理是Java编程中非常重要的一部分,它允许我们在程序运行时捕获并处理错误或不预期的行为,而不是让程序直接崩溃,本文将介绍Java中如何捕获异常,以及常用的异常处... 目录前言什么是异常?Java异常的基本语法解释:1. 捕获异常并处理示例1:捕获并处理单个异常解释:

Python自定义异常的全面指南(入门到实践)

《Python自定义异常的全面指南(入门到实践)》想象你正在开发一个银行系统,用户转账时余额不足,如果直接抛出ValueError,调用方很难区分是金额格式错误还是余额不足,这正是Python自定义异... 目录引言:为什么需要自定义异常一、异常基础:先搞懂python的异常体系1.1 异常是什么?1.2

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买