微信公众号:内核小王子
觉得可以的话欢迎关注
场景:公司对外网关对很多外部商户开放,运行多年一直正常,昨天某一个客户调用我们接口的时候频繁报connectiontimeout,异常如下:
该异常来自于httpclient,原因是创建连接超时,也就是tcp进行三次握手的时候失败,或者握手报文没有到达服务端。分析可能有如下原因:
- 1.报文发送太频繁,而客户防火墙性能太差,将报文丢弃
- 2.我们服务端性能过载,accept()方法执行太慢,syn队列或者accept队列满了,导致握手报文被丢弃 ,关于tcp三次握手和发送流程刻参考这篇文章
经过排查后,都不是上面两个原因,目前现象ping包是正常的,执行以下nc命令 ,偶尔会失败,大部分时候成功
while true; do nc -w 2 -zv open.xxx.com 443 ;sleep 1;done
为了排查上面问题,我们先回顾一下这些概念,因为我发现很多人并不清楚短连接加上keepalive和长连接的区别。
长连接: tcp三次握手后,操作系统会通过发送tcp心跳包来保证连接不会因为空闲时间限制被回收,基于长连接客户端可以先发送一个请求,不用等服务端返回立即在发送一个请求
短连接: tcp三次握手后,请求一次等到收到回复后就会进行四次挥手断开连接。
http : 目前http都是短连接,并且在1.1版本中采用了keep-alive机制进行重用。短连接加上keep-alive可以让连接保持活性,但是她和长连接的区别是长连接可以不用等第一个请求的回复收到就可以发送第二个请求,因为tcp是一个流,而短连接虽然通过keep-alive保持连接的活性,但他必须等到第一个请求的回复收到后,才能发送第二个请求,一般会用一个连接池进行复用。浏览器请求一个网页,最开始是通过一个连接进行请求,在收到报文回复后解析发现要加载更多的资源,例如图片和js等资源,会在开多个连接,并发的请求服务端,而这些连接会通过keep-alive保持活性放到一个连接池重用,keep-alive机制是在服务端实现的,例如在tomcat的server.xml里面配置,默认一般为1分钟.
<Connector port="8080" maxThread="50" minSpareThreads="25" maxSpareThread="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true" maxKeepAliveRequest=100 keepAliveTimeout=60000/>
keepalive : 刚刚说到http的keepalive是在服务端实现的,并且是针对短连接的,有了keepalive的短连接我们一般称为持久连接,而tcp的长连接也需要keepalive机制,客户端和服务端会周期的发送探活报文,这个我们可以通过wireshake进行抓包获取到,有了长连接我们就可以针对这个连接发送请求,并且可以不用等服务端返回后在发送后续请求,为什么http没有用长连接,一般请求一个网页后用户会花时间进行浏览,没有必要用长连接长时间占有资源,但是加载网页的时候为了加快速度,浏览器进行了并发,会同时创建多个连接进行请求,为了防止创建多个连接导致服务器压力太大,所以浏览器限制了同一个域名同时请求的连接数,所以服务端想要加快客户端访问速度可以将资源放到不同的域名来规避浏览器的限制,http没有采用长连接的另一个原因是在http1.1以及之前的版本,一个请求报文和回复报文并不能匹配,并没有定义一个序列号让response和request对应,这样当同时发送多个request之后,客户端在收到response之后不知道和那个request对应,而在http2.0中有定义,所以目前的浏览器都是创建多个连接进行并发,但是单个连接内部必须等上一个请求回复后才能发送下一个请求。
而我们经常会在应用层也会实现一层keepalive探活,例如netty里面IdleSateHandler,很多rpc框架例如dubbo也会单独在应用层实现一层探活机制,有两个原因,一个是应用层可以基于此做一些高可用相关的检测,另外由操作系统发出的tcp探活包仅仅针对网络连接,过于底层,不够灵活,并且即使应用层没有资源处理网络请求底层仍然会对探活包进行响应,而基于应用层就更加灵活,例如服务端可以主动管理连接,客户端也可以主动检测服务端是否可用。
好了会到正题
最终我们发现商户是三台服务器一起请求的,而三台服务器应该是经过nat后是同一个ip,那么很可能是触发了tcp中的一个时间戳的限制,也就是如果同一个ip的请求会记录其时间戳并进行比较,下次发送握手报文的时候,如果时间戳比上一次请求时间小,那么会将该握手报文丢弃,如果同一个ip是同一个机器一般不会有问题,然而三台机器相同ip但是时间戳可能不相同,如果在大批量发送请求的时候很可能会触发该规则。我们询问了下运维的千夜,他确实是在两会期间排查网络问题的时候加了如下参数,开启了回收机制:
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
而系统默认
net.ipv4.tcp_timestamps = 1 表示开启时间戳校验
千夜将配置还原后,执行sysctl -p 后没有在报超时异常。
历史文章: