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

相关文章

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

SpringBoot中4种数据水平分片策略

《SpringBoot中4种数据水平分片策略》数据水平分片作为一种水平扩展策略,通过将数据分散到多个物理节点上,有效解决了存储容量和性能瓶颈问题,下面小编就来和大家分享4种数据分片策略吧... 目录一、前言二、哈希分片2.1 原理2.2 SpringBoot实现2.3 优缺点分析2.4 适用场景三、范围分片

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

Java空指针异常NullPointerException的原因与解决方案

《Java空指针异常NullPointerException的原因与解决方案》在Java开发中,NullPointerException(空指针异常)是最常见的运行时异常之一,通常发生在程序尝试访问或... 目录一、空指针异常产生的原因1. 变量未初始化2. 对象引用被显式置为null3. 方法返回null