当前位置: 首页 > 后端技术 > Java

客户端请求服务器莫名超时

时间:2023-04-01 13:32:54 Java

背景:客户端请求服务器的域名进行查询操作,偶尔有200ms+的延迟,请求服务器的执行速度在4ms左右。这个问题很奇怪,我们决定研究一下。下面记录一下当时问题定位的详细定位&解决过程:1.分析代码服务器系统是一个普通的spring-bootweb项目,使用集成tomcat。分析代码后发现没有特别的地方,没有特别的过滤器或者拦截器,所以初步排除是业务代码问题。2、确定调用链路3、故障排除及分析3.1.网络层问题,抓包(20210701)判断耗时位置是客户端调用F5,还是F5调用服务端?3.1.1服务器ack上有很多RST。通过跟踪RSTHTTP流,发现在最后一次关闭FIN后,服务端向客户端发送FIN请求关闭连接,然后服务端向客户端发送消息,导致RST。抓包如下:怀疑是耗时连接无法复用。查看代码后发现客户端并没有使用连接池,于是客户端增加http连接池来解决这个问题。3.1.2网络耗时较长,在2.1.1优化抓包后,仍然存在调用超时现象。现在我们通过wireshark抓包观察,发现client和F5之间没有超时。客户端发送请求后,随机收到F5的ACK;F5发送请求后,每隔40ms收到服务器的ack。这就引出了另一个问题,就是TCP神奇的40ms(https://cloud.tencent.com/dev...大家可以自行去笔记中的链接学习,这里篇幅有限,所以不细说了3.2.网络层没有波动考虑应用层,开始考虑serversocket连接和server容器的方向3.2.1.serversocket连接跳过F5加载并直接使用IP:port方式请求服务器(20210713)20210714观察日志:客户端发起请求:2021-07-1414:05:02.670服务器收到请求:2021-07-1414:05:02.939服务端返回结果:2021-07-1414:05:03.280客户端收到返回:2021-07-1414:05:03.280客户端整体请求时间:610ms服务端程序耗时:546ms分析服务请求抓包(d1134184-2a52-4915-8e63-7f32ed6d274d)出现大量乱序重传,暂时怀疑是抓包工具图片延迟问题。这个问题用tcpdump抓包后就可以解决了。20210716tcpdumpcapture确认以上问题是抓包工具引起的。3.2.2。服务器容器服务器使用的容器是tomcat8.5,内存是NIO模型,没有阻塞对于tomcatNIO来说,拥堵情况是由一系列的框架类和数据读写类组成的。同时,这些类运行在不同的线程中,共同维护着整个tomcat的NIO架构。包括监听原始socket的acceptor线程,监听原始socket上注册的事件是否发生的poller线程event线程,以及读写数据和运行servletAPI的tomcatio线程。当需要多次读写数据时,监听在原socket上注册的读写事件的blockpoller事件线程。即NIO的整体架构运行了四种线程:Acceptor线程Poller线程TomcatIO线程BlockPoller线程这里注意:tomcat有acceptor线程接受socket连接,然后有worker线程进行业务处理。对于client端的请求,流程如下:tcp的三次握手建立连接。在连接建立过程中,OS会维护一个半连接队列(synqueue)和一个全连接队列(acceptqueue)。第三次握手后,服务器收到客户端的ack,然后进入establish状态,然后连接从syn队列移到accept队列。tomcat的acceptor线程负责从accept队列中取出连接,接受连接,然后交给工作线程处理(读取请求参数,处理逻辑,返回响应等;如果连接是不是keepalive,关闭connection连接,然后把worker线程释放回线程池,如果是keepalive,等待下一个数据包的到来,直到keepAliveTimeout,然后关闭连接,释放回线程pool),然后去accept队列中获取连接(当当前socket连接超过maxConnections时,acceptor线程自身会阻塞等待,等待连接掉线后再处理accept队列中的下一个连接).acceptCount是指接受队列的大小。具体的tomcat线程介绍:https://segmentfault.com/a/11...查看服务器代码后发现tomcat线程相关的参数没有配置,即默认值3.2.2.1一直在调整原来的服务容器参数(20210708)怀疑是服务器线程设置太小,一直在创建新线程耗时,所以调整服务器配置如下:server:port:${port:8080}tomcat:max-threads:1000min-spare-threads:500accept-count:1000max-connections:10000connection-timeout:300000投产后发现客户端还是原来的情况,这次调整失败.3.2.2.2服务器离线AServerisonlyforclientIPcalls(20210716)怀疑accept-count设置过大,导致请求一直在队列中,但还不到1000,没有将创建新线程。20210716去掉服务器端的A服务器,进入观察这个调用抓包耗时137ms:客户端发送请求:2021-07-1614:10:11.600服务器在14:10:11.602897收到请求request在server容器中耗时为了验证上面的判断,我们打印tomcat的access_log日志,让服务器A服务器保持离线状态(20210720)20210721观察发现tomcat容器收到了请求:2021-07-2114:18:14.872theservletstartedtoexecute:2021-07-2114:18:15.0143.2.2.3见光,升级tomcat容器这个定位问题是tomcat容器造成的。但是tomcat的问题出在哪里呢?我们继续往下看。首先我们这里查看了客户端的请求量,每分钟不到1000个。我们暂时不考虑tomcat队列的积压。然后,我们打算把我们的神器送到这里。打开debug日志,观察看看会不会有什么惊喜(其实一直怀疑是springBoot对tomcat的支持不是那么友好)没想到,打开debug日志后,竟然发现了这么个问题!在[uploading...]()之后,我们找到了源代码并找到了OrderedRequestContextFilter。我们在网上查阅了资料(https://segmentfault.com/a/11...),仔细研究了上面的问题后,我们开始“监控”我们的服务:在客户端执行arthastrace后![上传。..]()这行代码加载了31次,一共耗时140ms,这里的现象和帖子里的差不多,后来我们分析了服务端的包结构,如果有下面的jar包在这里,我们决定不再跟踪,直接升级tomcat版本到8.5.40,升级方法如下:compile('org.springframework.boot:spring-boot-starter-web'){excludemodule:"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部署,观察日志如下:经过一天的观察,调用耗时明显减少,但还是偶尔需要很长时间(超过10毫秒)。日志分析如下:本次消费不在容器层,为网络波动正常。3.2.2.4将容器改为undertow容器3.3最终定位为Tomcatembedbug(以下摘自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.9tomcat:ApacheTomcat/8.5。23升级tomcatembed版本到8.5.40+解决这个问题,新版本已经修复到这个版本及以上,可以解决这个问题。感谢1.感谢帮助解决这个问题的同事2.感谢分享经验的高手。感谢您的帮助。

猜你喜欢