记一次Apache HTTP Client问题排查

news2024/11/16 7:29:28

现象

通过日志查看,存在两种异常情况。
第一种:开始的时候HTTP请求会报超时异常。

762663363 [2023-07-21 06:04:25] [executor-64] ERROR - com.xxl.CucmTool - CucmTool|sendRisPortSoap error,url:https://xxxxxx/realtimeservice/services/RisPort
org.apache.http.conn.HttpHostConnectException: Connect to xxx [/xxx] failed: 连接超时

第二种:突然没有新的HTTP请求日志了,现象就是HTTP请求后,一直卡主,等待响应。

HTTP Client代码

先查看一下HTTP的请求代码
HTTP Client设置

private static CloseableHttpClient getHttpClient() {
    SSLContextBuilder builder = new SSLContextBuilder();
    CloseableHttpClient httpClient = null;
    try {
        builder.loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        });
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(),
            SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    } catch (Exception e) {
        log.error("getHttpClient error:{}", e.getMessage(), e);
    }
    return httpClient;
}

请求方式(未设置http connection timeout 和 socket timeout)

HttpPost httpPost = new HttpPost(url);
try(CloseableHttpResponse response = getHttpClient().execute(httpPost)) {
    HttpEntity httpEntity = response.getEntity();
    if (httpEntity != null) {
        System.out.println(EntityUtils.toString(httpEntity, "UTF-8"));
    }
} catch (Exception e) {
    log.error("test,url:" + url, e);
}

进一步分析

连接超时

通过本地debug先找到了socket连接处的代码,如下所示。
socket连接请求在java.net.Socket#connect(java.net.SocketAddress, int)这里

image.png

可以看到如果不设置connect timeout,在java层面默认是无限超时,那实际是要受系统层面影响的。我们都知道TCP建立连接的第一步是发送syn,实际这一步系统层面会有一些控制。

Linux环境

linux下通过net.ipv4.tcp_syn_retries控制sync的超时情况

Number of times initial SYNs for an active TCP connection attempt will be retransmitted. Should not be higher than 127. Default value is 6, which corresponds to 63seconds till the last retransmission with the current initial RTO of 1second. With this the final timeout for an active TCP connection attempt will happen after 127seconds.

默认重试次数为6次,重试的间隔时间从1s开始每次都翻倍,6次的重试时间间隔为1s, 2s, 4s, 8s, 16s,32s, 总共63s,第6次发出后还要等64s都知道第6次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s + 64 = 127 s,TCP才会把断开这个连接。
第 1 次发送 SYN 报文后等待 1s(2 的 0 次幂),如果超时,则重试
第 2 次发送后等待 2s(2 的 1 次幂),如果超时,则重试
第 3 次发送后等待 4s(2 的 2 次幂),如果超时,则重试
第 4 次发送后等待 8s(2 的 3 次幂),如果超时,则重试
第 5 次发送后等待 16s(2 的 4 次幂),如果超时,则重试
第 6 次发送后等待 32s(2 的 5 次幂),如果超时,则重试
第 7 次发送后等待 64s(2 的 6 次幂),如果超时,则断开这个连接。

mac环境

mac场景下是通过net.inet.tcp.keepinit参数控制syn超时(默认是75s)。
可以通过下面的命令查看

sysctl -A |grep net.inet.tcp.keepinit

net.inet.tcp.keepinit: 75000
通过telnet验证,确实是75s超时

image.png
tcpdump抓包也可以看到一直进行syn重试。

image.png

读取超时

Apache Http Client 默认的socket read timeout 是0。
image.png
image.png
通过代码注释可以看到,如果soTimeout是0的话,就意味着读取超时不受限制,但是实际上这里也会有系统层面的控制,下面从HTTP层面和TCP层面做一下分析。

HTTP Keep-alive

首先,Apache httpClient 4.3版本中,如果请求中未做制定,那么默认会使用HTTP 1.1,代码如下。

public static ProtocolVersion getVersion(HttpParams params) {
    Args.notNull(params, "HTTP parameters");
    Object param = params.getParameter("http.protocol.version");
    return (ProtocolVersion)(param == null ? HttpVersion.HTTP_1_1 : (ProtocolVersion)param);
}

对于HTTP 1.1版本来说,默认会开启Keep-alive,并使用默认的keep-alive策略。

public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {

    public static final DefaultConnectionKeepAliveStrategy INSTANCE = new DefaultConnectionKeepAliveStrategy();

    public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
        Args.notNull(response, "HTTP response");
        final HeaderElementIterator it = new BasicHeaderElementIterator(
            response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            final HeaderElement he = it.nextElement();
            final String param = he.getName();
            final String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(final NumberFormatException ignore) {
                }
            }
        }
        return -1;
    }

}

其基本原理就是HTTP场景下,当客户端与服务端建立TCP连接以后,Httpd守护进程会通过keep-alive timeout时间设置参数,一个http产生的tcp连接在传送完最后一个响应后,如果守护进程在这个keepalive_timeout里,一直没有收到浏览器发过来http请求,则关闭这个http连接。
这里有两点要注意:

  • 可以看到keep-alive的超时时间是服务端返回时,http client在响应头中解析到的。如果一直未收到服务端响应,那么客户端会认为keep-alive一直有效;-1的返回值也是如此。
  • 如果服务端有响应,如果服务端有响应,那么就会按照服务端的返回设置keep-alive的timeout,当timeout到期后,就会从http client pool中移除,服务端关闭该TCP连接。

下面是一个成功的HTTP client响应信息,可以看到服务端给出的keep-alive时间是60s。

image.png
image.png

后续对于这个连接不做任何处理,可以看到60s以后断开了连接。

image.png

TCP下的keep-alive机制

TCP连接建立之后,如果某一方一直不发送数据,或者隔很长时间才发送一次数据,当连接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,连接还需不需要保持,这种情况在TCP协议设计中keep-alive的目的。
TCP协议中,当超过一段时间之后,TCP自动发送一个数据为空的报文(侦测包)给对方,如果对方回应了这个报文,说明对方还在线,连接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为连接丢失,没有必要保持连接。
在Linux系统中有以下配置用于TCP的keep-alive。


tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

在MAC下对应的配置如下(单位为ms)


net.inet.tcp.keepidle: 7200000
net.inet.tcp.keepintvl: 75000
net.inet.tcp.keepcnt: 3

也就是说在Mac系统中,最少经过2小时3分钟45秒才可以发现一个「死亡」连接。

对于TCP的keep-alive是默认关闭的,可以通过应用层面打开。
对于Java应用程序,默认是关闭的,后面我们模拟在客户端开启该配置。

public static final SocketOption SO_KEEPALIVE
Keep connection alive.
The value of this socket option is a Boolean that represents whether the option is enabled or disabled. When the SO_KEEPALIVE option is enabled the operating system may use a keep-alive mechanism to periodically probe the other end of a connection when the connection is otherwise idle. The exact semantics of the keep alive mechanism is system dependent and therefore unspecified.
The initial value of this socket option is FALSE. The socket option may be enabled or disabled at any time.

首先,修改mac的keep-alive设置,将时间调短一些。

sysctl -w net.inet.tcp.keepidle=60000 net.inet.tcp.keepcnt=3 net.inet.tcp.keepintvl=10000 

net.inet.tcp.keepidle: 60000
net.inet.tcp.keepintvl: 10000
net.inet.tcp.keepcnt: 3

依然通过HTTP Client开启keep alive配置

SocketConfig socketConfig = SocketConfig.DEFAULT;
SocketConfig keepAliveConfig = SocketConfig.copy(socketConfig).setSoKeepAlive(true).build();
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultSocketConfig(keepAliveConfig).build();

通过HTTP Client请求服务端一个耗时很长的接口,并通过TCP抓包可以看到以下内容
每隔60s,客户端会向服务端发送保活的连接。

image.png
再来验证一下,如果服务端此时不可用的情况。
使用pfctl工具,模拟服务端不可达。
可以看到客户端每隔10s,累计尝试3次,然后就会关闭该连接。

image.png

image.png

image.png

回归问题

连接超时问题

此时服务器因为个别原因,无法正常连接。
由于HTTP Client未设置对应的超时时间,所以会依据系统的net.ipv4.tcp_syn_retries进行重试。
该异常客户端可以感知到。

请求卡主问题

当某个时间HTTP Client与服务器建立的正常的TCP连接后,服务器发生了异常,此时由于以下原因叠加

  • HTTP Client未设置socket读取超时时间
  • HTTP keep-alive也由于服务端未响应默认不受限制
  • 另外TCP层面的keep alive也没有手动开启

所以此时客户端会一直持有该TCP连接等待服务器响应。对应到下图的话,也就是橙色部分。

image.png
当然最直接的解决方案就是设置socket read timeout时间即可。

RequestConfig requestConfig = RequestConfig.custom()
    .setConnectionRequestTimeout(1000)
    .setSocketTimeout(1000)
    .setConnectTimeout(1000).build();
httpPost.setConfig(requestConfig);

当时间到了会报read timeout 异常。

image.png

总结

  • 当我们使用HTTP Client的时候,需要结合业务需要合理设置connect timeout和 socket timeout参数。
  • 当进行问题追踪时,需要利用HTTP和TCP的一些知识,以及tcpdump等抓包工具进行问题验证。

参考文档

【1】 一文说清楚 Linux TCP 内核参数_linux tcp参数_DBA大董的博客-CSDN博客
【2】 服务端挂了,客户端的 TCP 连接还在吗?
【3】JAVA socket keep alive 说明https://docs.oracle.com/javase/8/docs/api/java/net/StandardSocketOptions.html#SO_KEEPALIVE

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/799483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【NodeJs】如何将Markdown文件生成HTML文件在线浏览

经常用的编辑器是Markdown,有自带预览排版效果功能的,预览的是HTML网页,如果想要将它转换成HTML网页文件,要怎么做呢。 首先,借助Node的插件来做,在使用前,确保电脑已安装了NodeJS应用&#xf…

git本地库和远程库的相关操作命令

目录 一、分支概念&#xff1a; 二、 本地库分支管理&#xff1a; 1. 查看分支情况&#xff1a; 命令1&#xff1a;git branch 2. 新建分支 命令1&#xff1a; git branch <分支名> 命令2&#xff1a; git branch <新建分支名> <源分支名> 命令3&…

【温故而知新】【中间件】Redis为什么这么快?

时间&#xff1a;2023年07月27日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 【温故而知新】【中间件】Redis为什么这么快&#xff1f;_小蒋聊技术_免费在线阅读收听下载 - 喜马拉雅欢迎收听小蒋聊技术的类最新章节声音“【温…

【渗透测试】PNG图片隐藏部分恢复

1、图片原尺寸还原方法一 缺点就是有点慢&#xff0c;毕竟遍历的次数比较多 import binascii import struct import sysfilename sys.argv[1] crcbp open(filename, "rb").read() # 打开图片 crc32frombp int(crcbp[29:33].hex(), 16) # 读取图片中的CRC校验值 …

【密码学】三、AES

AES 1、AES产生2、数学基础2.1有限域GF(2^8^)2.1.1加法运算2.1.2乘法运算2.1.3x乘运算2.1.4系数在GF(2^8^)上的多项式 3、AES算法描述3.1字节代换3.2行移位3.3列混合3.4轮密钥加3.5密钥扩展 1、AES产生 征集AES算法的活动&#xff0c;目的是确定一个非保密的、公开的、全球免费…

Cesium态势标绘专题-直角箭头(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

国产化 | 记一次基于达梦创建数据库模式思考过程

开篇 首先&#xff0c;我们先来了解一下达梦数据库中用户与模式的概念&#xff0c;以及用户与模式之间的关系。 用户&#xff1a;主要是用来登录连接数据库&#xff0c;以及操作数据库对象等等。 模式&#xff1a;数据库中相关对象的集合。 关系&#xff1a;用户&#xff0…

测评7大热门订房APP,用好结尾这三点,分分钟帮你省掉好多钱

出去旅行预订酒店的时候&#xff0c;相信大家都有过纠结&#xff0c;那么多订房渠道到底应该选哪家。难道要把每个APP都下载下来试一遍吗&#xff1f; 所以&#xff0c;今天笔者给大家带来各大订房APP的测评。 先说结论&#xff1a;仅从性价比来看&#xff0c;民宿优于酒店&a…

Cesium态势标绘专题-燕尾箭头(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

LeetCode | Bit Manipulation, heap | 190. 191. 136. 137. 201.215.

190. Reverse Bits 191. Number of 1 Bits 一个一个数就行了。比较简单。 136. Single Number XOR的点在于&#xff0c;两个一样的数字a^a&#xff0c;结果是0. 且XOR是可以换位置的&#xff0c;所以把所有东西XOR在一起&#xff0c;剩下的就是单呗的。 137. Single Number I…

Python GDAL为具有多个波段的大量栅格图像绘制像素随时间变化走势图

本文介绍基于Python中的gdal模块&#xff0c;对大量长时间序列的栅格遥感影像文件&#xff0c;绘制其每一个波段中、若干随机指定的像元的时间序列曲线图的方法。 在之前的文章Python中GDAL批量绘制多时相栅格遥感影像的像元时间序列曲线图&#xff08;https://blog.csdn.net/z…

wireshark导出H264裸流

导出H264裸流 安装wireshark下载rtp_h264_extractor.lua脚本配置lua脚本重启wireshark筛选 安装wireshark 下载抓包工具&#xff1a;首先&#xff0c;您需要下载并安装一个网络抓包工具&#xff0c;例如Wireshark&#xff08;https://www.wireshark.org&#xff09;或tcpdump&…

开源数据库 | 记一次在麒麟操作系统上适配openGauss进阶之旅

引入 适配 | 认证-Kylin V10 ARM 麒麟操作系统openGauss数据库 开篇 1、数据库架构 百度百科&#xff1a;openGauss 是一款全面友好开放&#xff0c;携手伙伴共同打造的企业级开源关系型数据库。openGauss采用木兰宽松许可证v2发行&#xff0c;提供面向多核架构的极致性能、全…

linux安装nginx遇到的报错

1、Linux如何修改只读文件&#xff08;以设置自动连网为例&#xff09; vim /etc/sysconfig/network-scripts/ifcfg-ens33 然后提示 E45&#xff1a;已设定选项“readonly”&#xff08;请加&#xff01;强制执行&#xff09; 如果需要强制修改&#xff0c;可以使用&#xff0…

关于idea如何成功运行web项目

导入项目 如图 依次选择 file - new - Project from Existing Sources 选择存放的项目目录地址 如图 导入完成 点击ok 如图 依次选择 Create project from existing sources 点击next如图 &#xff0c;此处默认即可 点击 next如图 点击next有该提示 是因为之前导入过…

抖音seo源码开发源代码开发技术分享

一、 抖音SEO源码开发&#xff0c;需要掌握以下技术&#xff1a; 抖音API接口&#xff1a;抖音提供了丰富的API接口&#xff0c;包括用户信息、视频信息、评论信息等。 数据爬取技术&#xff1a;通过抓包分析抖音接口的数据结构&#xff0c;可以使用Python等编程语言编写爬虫程…

Elasticsearch Query DSL

Elasticsearch Query DSL 这里使用的 Elasticsearch 的版本为 7.12.1。 1、基本概念 1.1 文档(Document) ElasticSearch 是面向文档的&#xff0c;文档是所有可搜索数据的最小单位&#xff0c;例如 MySQL 的一条数据记录。 文档会被序列化成为 json 格式&#xff0c;保存在…

B076-项目实战--宠物上下架 展示 领养 收购订单

目录 上下架功能提供后台宠物列表实现 前台展示前台宠物列表和详情展示店铺展示 领养分析前台后端PetControllerPetServiceImpl 订单需求分析可能产生订单的模块订单模块额外功能 订单设计表设计流程设计 集成基础代码收购订单创建订单前端后端 上下架功能提供 后台宠物列表实…

生成虚拟淘宝购买记录截图图片制作

大家都知道&#xff0c;淘宝购买记录截图在某些情况下非常重要&#xff0c;但手动制作却非常繁琐&#xff0c;耗费时间和精力。如果你也遇到了这个问题&#xff0c;那么不妨试试淘宝订单生成器&#xff0c;它能够帮助你轻松生成淘宝购买记录截图&#xff0c;提升工作效率。 虚拟…

Docker 容器高级操作

Docker容器高级操作 Docker容器创建、停止、启动、删除等基础操作上篇已述,然Docker容器被广大开发者青睐,不可能只有如此简单的功能,必有高阶功法。那么接下来 让我们一同走进容器操作的高级篇,领略其高级操作的魅力。 查看容器 docker ps -a | grep tomcat [root@tudou…