介绍所有服务器TCP连接问题时,我们首先需要了解一下TCP的四次挥手是怎么一个运作机制,有利于我们解决后续遇到TCP状态异常时,辅助判断怎么处理。
有一个点需要注意一下,我们的服务器通常是作为server端存在,但同时也可以发起请求到第三方,作为client端存在。因此server既会有服务端的状态也会有客户端的状态,优先可以判断是服务端的状态异常还是作为客户端的状态异常。
1. keepAlive连接过多
突然接到服务器报警,访问非常慢,系统接近崩溃的状态。登录到服务器上查询对应的服务端口的TCP状态,发现TCP连接中ESTAB非常多,达到了3万多。拖慢了系统的访问,导致后续正常连接进不来,系统处于不稳定状态。
上面截图是我从一台服务器上截图下来的。可以查看TCP各个状态的数量。
TCP状态类型 | 数量 | |
---|---|---|
LISTEN | 1 | 本机8000端口启动监听端口 |
LAST_ACK | 1 | 等待客户端发送关闭连接的指令 |
ESTABLISHED | 8711 | 长连接,传输数据。 |
客户端和服务端三次握手后,建立了长连接。但是太多长连接,如果达到一定的数量(一般是6万多)会造成资源紧张,服务器无法处理。虽然不是错误的状态,仍然需要通过添加定时清理没有请求的长连接来保证业务系统的正常运行。
我们可以通过ss命令查看ESTABLISHED状态是否开启了定时,通过配置系统TCP keepalive参数来进行定时清除。可以看到最后一列是没有显示keepalive的定时状态。 ss -aeno | grep 8000 | head -n 20
- 没有开启定时
- 开启定时后
可以看到多了一个timer,里面用逗号分隔的三个值,分别代表了目前是keepalive状态、重发该消息的间隔时间、尝试的次数。具体详细配置在/etc/sysctl.conf
中。
net.ipv4.tcp_keepalive_intvl | 探测消息未获得响应时,重发该消息的间隔时间(秒)。 |
---|---|
net.ipv4.tcp_keepalive_probes | 在认定TCP连接失效之前,最多发送多少个keepalive探测消息。 |
通过不断刷新命令,可以看到时间在逐步减少。当到0时,尝试次数也会加1。直到次数到3时会杀掉这个长连接。至此长连接的配置就完成。配置后连接数减少的非常多,系统业务运行正常。
2. CLOSE WAIT过多
如果出现过多的状态,基本可以断定是服务端代码出现了问题。根据上面的挥手示例图,可以得知是服务端在处理业务的过程中卡住了,没有返回数据或者没有发送关闭连接的请求。这个时候我们可以借用阿里的arthas工具。
启动arthas的jar包
连上后,可以输入dashboard
命令来观察一下整体线程的情况。代码已经被修复,所以没有close wait请求了,存在部分的time waiting的请求。
在输入thread
命令来查看线程的详细状态,可以在后面带上线程的id来打印线程的堆栈信息。通过查看后得知是在发起第三方网络请求时,出现网络超时,不能返回正确的数据,导致连接没有被回收。
查看代码后得知里面其实每一次网络请求都new了一个restTemplate(BUG)。出现网络错误的时候,连接没有被正确的回收。因此我们直接改掉这一块逻辑,注入一个全局的restTemplate即可。发布到线上,问题解决。
3. TIME WAITING过多
方式跟CLOSE WAIT一样,先排除掉是否是程序的问题。通过线程堆栈信息我们可以得知是tomcat线程池在没有请求时会暂停部分请求。这里其实可以不用处理,等待tomcat线程池自己的回收机制即可
为了防止出现timewait过多,导致业务系统出现崩溃的情况。需要配置如下参数
net.ipv4.tcp_tw_reuse | 表示是否允许将处于TIME-WAIT状态的Socket(TIME-WAIT的端口)用于新的TCP连接。 |
---|---|
net.ipv4.tcp_max_tw_buckets | 该参数设置系统的TIME_WAIT的数量,如果超过默认值则会被立即清除。tcp_max_tw_buckets默认值大小会受实例内存的影响,最大值为262144。 |
开启重用,当连接进来时,直接复用空闲的连接。设置timewait数量最大值,超过直接触发强制清除。 详细的TCP内核配置参数,[参照](Linux系统常用内核网络参数介绍与常见问题处理_云服务器 ECS-阿里云帮助中心 (aliyun.com))