• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于java:客户端请求服务端莫名出现超时

java 搞代码 3年前 (2022-01-27) 72次浏览 已收录 0个评论

背景:
客户端申请服务端域名,进行查问操作,偶然呈现200ms+提早,且对于该申请服务端执行速度4ms左右,此问题十分诡异,决定对此进行钻研. 上面记录下过后具体的定位&解决流程
问题定位:
1、剖析代码
服务端零碎是一个常见的spring-boot web工程,应用了集成的tomcat。剖析了代码之后,发现并没有非凡的中央,没有非凡的过滤器或者拦截器,所以初步排除业务代码问题.
2、确定调用链路

3、问题排查剖析
3.1、网络层问题,抓包 (20210701)
确定耗时地位是 客户端调用F5,还是 F5 调用 服务端 ?
3.1.1 服务端ack呈现大量 RST

通过跟踪RST的HTTP 流 发现:在FIN最初一次敞开之后 , 服务端向客户端发送了 FIN 敞开连贯的申请,而后服务端又向客户端发了报文导致RST. 抓包如下:

狐疑是连贯不能复用导致的建联耗时,经排查代码发现,客户端未应用连接池,因而 客户端减少HTTP 连接池来解决此类问题 .
3.1.2 网络耗时长,抓包
通过2.1.1 的优化之后,还是存在调用超时的景象,当初咱们通过wireshark 抓包察看,发现客户端与F5之间无超时状况,客户端发出请求后,随机收到了F5的ACK;F5 发出请求后,距离40ms 收到服务端的ack , 此处就引出了另外一个问题,就是 TCP 神奇的40 ms (https://cloud.tencent.com/dev… 大家能够自行去备注的链接去研读,这里篇幅工夫无限,不在进行赘述 .
3.2、网络层无稳定,思考应用层
开始思考服务端 socket 连贯 和 服务端容器方向
3.2.1. 服务器socket连贯
跳过F5 负载,间接应用IP:port 的形式申请服务端(20210713)
20210714 察看日志:

客户端发动申请: 2021-07-14 14:05:02.670
服务端收到申请: 2021-07-14 14:05:02.939
服务端返回后果: 2021-07-14 14:05:03.280
客户端收到返回: 2021-07-14 14:05:03.280
客户端整体申请耗时: 610ms 服务端程序耗时: 546ms
对服务申请抓包进行剖析(d1134184-2a52-4915-8e63-7f32ed6d274d)

呈现大量乱序重传,临时狐疑是抓包工具镜像提早导致, 此问题待 应用 tcpdump 抓包后给出论断.
20210716 tcpdump 抓包
确认了以上问题是抓包工具导致 .
3.2.2. 服务端容器
服务端应用的容器是 tomcat 8.5, 内存是NIO 模型,不存在阻塞状况。对于 tomcat NIO 来说,是由一系列框架类和数据读写类来组成的,同时这些类运行在不同的线程中,独特维持整个 tomcat NIO 架构。包含原始 socket 监听的acceptor 线程,监测注册在原始 scoket 上的事件是否产生的 poller thread 事件线程,进行数据读写和运行 servlet API 的 tomcat io 线程。当数据须要屡次读写的时候,监测注册在原始 scoket 上的读写事件的 block poller 事件线程。 即 NIO 整体架构运行着4种线程:
Acceptor 线程
Poller 线程
Tomcat IO 线程
BlockPoller 线程
这里要留神:
tomcat有一个acceptor线程来accept socket连贯,而后有工作线程来进行业务解决。对于client端的一个申请进来,流程是这样的:tcp的三次握手建设连贯,建设连贯的过程中,OS保护了半连贯队列(syn队列)以及齐全连贯队列(accept队列),在第三次握手之后,server收到了client的ack,则进入establish的状态,而后该连贯由syn队列挪动到accept队列。tomcat的acceptor线程则负责从accept队列中取出该connection,承受该connection,而后交给工作线程去解决(读取申请参数、解决逻辑、返回响应等等;如果该连贯不是keep alived的话,则敞开该连贯,而后该工作线程开释回线程池,如果是keep alived的话,则期待下一个数据包的到来直到keepAliveTimeout,而后敞开该连贯开释回线程池),而后本人接着去accept队列取connection(当以后socket连贯超过maxConnections的时候,acceptor线程本人会阻塞期待,等连贯降下去之后,才去解决accept队列的下一个连贯)。acceptCount指的就是这个accept队列的大小。
具体tomcat的线程的介绍: https://segmentfault.com/a/11…

通过对服务端代码审查发现,未曾配置过tomcat线程相干参数,即始终应用默认值
3.2.2.1 调整原服务容器参数(20210708)
这里狐疑服务端线程设置过小,始终在创立新的线程上耗费工夫,所以对服务端配置进行调整,调整如下:
server:
port: ${port:8080}
tomcat:

max-threads: 1000
min-spare-threads: 500
accept-count: 1000
max-connections: 10000

connection-timeout: 300000
投产后发现,客户端仍存在原来的状况,此次调整失败.
3.2.2.2 下线服务端A服务器 仅供客户端IP调用 (20210716)
狐疑accept-count 设置过大,导致申请始终在队列中,然而还未满1000,不会创立新的线程导致。20210716 摘除服务端的A服务器,进入察看

本次调用 耗时 137ms
抓包:
客户端发送申请 : 2021-07-16 14:10:11.600


服务端接管申请在 14:10:11.602897

申请在服务端容器耗时
为了验证上述判断,咱们打印一下tomcat的access_log日志,并放弃服务端A服务器对外下线状态(20210720)
20210721 察看发现,
tomcat 容器收到申请: 2021-07-21 14:18:14.872
servlet 开始执行: 2021-07-21 14:18:15.014

3.2.2.3 看见光明,降级 tomcat 容器
本次定位问题为 tomcat 容器导致. 然而到底问题是在tomcat的什么中央呢? 咱们持续往下找
首先这里咱们查了下客户端的申请量,每分钟不到1000,临时不思考tomcat队列积压的问题
而后,这里打算寄出咱们的神器了,关上debug日志, 进行察看,看看会不会有惊喜(其实这里曾经狐疑springBoot 对 tomcat 的反对不是那么的敌对)
过不期然,再关上了debug日志之后,咱们发现了这样的一个问题

![上传中…]()

之后,咱们定位源代码,找到了OrderedRequestContextFilter。
咱们查阅了网上的材料后(https://segmentfault.com/a/11…

在认真研读了以上问题后,咱们开始对咱们的服务进行了“监督”:

对客户端进行arthas 的 trace 之后![上传中…]()

这行代码加载了 31 次,一共耗时 140ms. 这里的景象简直和帖子上的统一
后续咱们剖析了服务端的包构造,果然存在如下jar包

到这里,咱们决定不再进行trace, 间接降级tomcat版本到 8.5.40
降级办法如下:
compile(‘org.springframework.boot:spring-boot-starter-web’) {

    exclude module: "spring-boot-starter-tomcat"
}
compile 'org.apache.tomcat.embed:tomcat-embed-core:8.5.40'
compile 'org.apache.tomcat.embed:tomcat-embed-el:8.5.40'

// compile ‘org.apache.tomcat.embed:tomcat-embed-logging-juli:+’

compile 'org.apache.tomcat.embed:tomcat-embed-logging-juli:8.0.37'
compile 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.40'

降级于20210722 上线部署
20210723 察看日志如下:

察看一天,调用耗时均有明显降低,但依然偶有工夫长(超过10ms)景象 , 日志剖析如下:

本次耗时不在容器层,为网络稳定失常导致.

3.2.2.4 更换容器为undertow 容器
3.3 最终定位为Tomcat embed Bug(以下摘自 https://segmentfault.com/a/11…
3.3.1 为什么每次申请会加载Jar包内的动态资源
关键在于org.apache.catalina.mapper.Mapper#internalMapWrapper这个办法,该版本下解决申请的形式有问题,导致每次都校验动态资源。
3.3.2 为什么间断申请不会呈现问题
因为Tomcat对于这种动态资源的解析是有缓存的,优先从缓存查找,缓存过期后再从新解析。具体参考org.apache.catalina.webresources.Cache,默认过期工夫ttl是5000ms。
3.3.3 为什么本地不会复现
其实确切的说,是通过spring-boot打包插件后不能复现。因为启动形式的不同,tomcat应用了不同的类去解决动态资源,所以没问题
解决方案:
1、降级tomcat-embed版本即可,呈现bug的版本为
springBoot:1.5.9 tomcat: Apache Tomcat/8.5.23
降级tomcat embed版本至8.5.40+即可解决此问题,新版本曾经修复了
2、降级spring boot版本
springboot 2.1.0.RELEASE中的tomcat embed版本曾经大于8.5.31了,所以间接将springboot降级至该版本及以上版本就能够解决此问题

致谢
1、感激为解决此次问题提供帮忙的共事们
2、感激把教训分享进去的大拿们
谢谢大家的帮忙


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:客户端请求服务端莫名出现超时

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址