tcp-issue

TCP状态变迁

在Web性能优化中,我们经常会调整很多TCP相关的性能参数,那么今天我们深入理解一下TCP协议的 11种状态变迁。我相信大家已经对于TCP连接的三次握手和四次挥手,并不陌生。在这其中TCP定义了 11种状态,下面我们来看看TCP的状态转换。

TCP状态变迁图

首先,在开始理解这个TCP状态转换图之前,我们需要明确,在实际情况下我们的Web服务器, 会同时充当两种角色:

充当服务器端的角色:就是作为Web服务器,等待客户端来进行请求。

充当客户端的角色:Web服务器作为请求的客户端请求后端的Redis或者MySQL等,或者请求其它 互联网上的服务。如果是作为反向代理,那么会请求后端的其它Web服务器。

备注:客户端和服务器端都可以主动发起关闭。

三次握手的状态变迁

要搞明白一个HTTP请求的时候,在TCP层面的11种状态转换,那么就需要我们的思路在客户端和 服务器端两种角色之间不停的变换。

客户端:

我们先从实线开始看,实线是客户端的状态变迁、虚线是服务器端的状态变迁。所以先看图的右半侧。

CLOSED:作为起始状态。

SYN_SENT:(CLOSED->SYN_SENT)

当某个应用进程在CLOSED状态下执行主动打开时,那就开始了TCP的三次握手,TCP将发送一个SYN, 然后这个客户端的状态由CLOSE转换为SYN_SENT状态。

注意,我们先不要考虑服务器端的状态,我们继续看客户端。

ESTABLISHED:(SYN_SENT-> ESTABLISHED)

那么这个时候根据TCP的三次握手,服务器端会发送一个带ACK的SYN给客户端,那么客户端收到后, 会发送一个ACK给服务器端,这个时候客户端的状态就会从SYN_SENT转换为ESTABLISHED,那么 这个状态我们应该非常熟悉,因为这个是数据传送发生的一个状态,表示已经建立TCP连接,并且 可以进行数据传送。可以看到图中标注的是收:SYN,ACK 发:ACK。

服务器端:

请注意!先不要往看ESTABLISHED以下的状态转换,我们现在马上把视角转换到服务器端,看看 服务器端在刚才的过程中都经历了哪些状态,我们需要看图的左半侧。

LISTEN:

比如我们客户端请求的是一个Web服务器,例如是Nginx服务,它监听在80端口,那么此时的TCP 状态为LISTEN,注意服务器端的起始状态也是CLOSE。

SYN_RCVD:(LISTEN->SYN_RCVD)

好的,现在处于LISTEN状态的Nginx服务器收到了客户端发过来的SYN,然后自身状态从LISTEN转换为 SYN_RCVD,我们想想此时客户端处于什么状态,与之相对应的SYN_SENT。服务器端是收SYN, 发送SYN和ACK给客户端。

ESTABLISHED:(SYN_RCVD> ESTABLISHED)

服务器端发送完毕SYN和ACK后,客户端会返回一个ACK,那么当服务器端接收到这个ACK后, 状态从SYN_RCVD转换到了ESTABLISHED。

好的,我们刚才已经详细说明了,在TCP建立连接过程中,客户端和服务器端的状态转换, 先不要继续往下看这个大图,我们在通过下面的小图按照TCP三次握手的方式,再次来梳理一下流程。

客户端执行主动打开,发送seq=x。SYN标志位置为1。状态从CLOSED到SYN_SENT。

服务器端被动打开,从CLOSED到LISTEND,收到客户端的SYN后,TCP标志位SYN、AC都置为1, 然后回复确认号ack=x+1,同时也发送一个序列号seq=y。状态从LISTEN转换为SYN_RCVD。

客户端收到服务器端的确认后,将TCP标志位ACK置为1,确认号是ack=y+1,发送序号seq=x+1, 此时进入ESTABLISHEN状态

服务器收到客户端发的信息后,也进入EXTABLISHED状态。

TCP四次挥手的连接状态:

 我们需要继续看这个状态转换的大图,这次我们要从ESTABLISHED开始往下看。这次我们还是
 从客户端开始看。

客户端FIN_WAIT_1:(ESTABLISHED->FIN_WAIT_1):客户端在ESTABLISHED的状态下, 发送FIN给服务器端要求关闭连接,这个时候,客户端的状态从ESTABLISHED转换为FIN_WAIT_1。

服务器端CLOSE_WAIT:(ESTABLISHED->CLOSE_WAIT)好的,我们先将客户端的状态暂停下, 回过来头来看服务器端,那么服务器端收到客户端发过来的FIN后,立即发送一个ACK确认,然后状态从ESTABLISHED转换到了CLOSE_WAIT。

客户端FIN_WAIT_2:(FIN_WAIT_1->FIN_WAIT_2)好的,我们再回来到客户端, 刚才服务器端给他发了一个ACK,也就是它收到了客户端的FIN的报文,这个时候,客户端马上从 FIN_WAIT_1转换到FIN_WAIT_2。

服务端LAST_ACK:(CLOSE_WAIT->LAST_ACK)

好的注意此时客户端不能再给服务端发送任何的数据了,但是服务端能给客户端发。此时就是我们 说的“半关闭”状态。那么此时服务端在干啥呢,他通知上层应用,比如HTTP,关闭连接,当上层 的数据发送完毕后,通知TCP说可以释放连接了,此时服务端给客户端发送一个FIN。然后自己从 CLOSE_WAIT状态转换为了LAST_ACK状态。那么如果上层应用吃吃不关闭呢?大家考虑下回发生什么。

客户端TIME_WAIT:(FIN_WAIT_2->TIME_WAIT)刚才服务器端给客户端发送了一个 FIN后进入到了LAST_ACK的状态,那么客户端收到这个FIN后,从FIN_WAIT_2转换为TIME_WAIT。 注意同时客户端会发回一个ACK给服务器端。

服务器端CLOSED:(LAST_ACK->CLOSED)我们先不管客户端,现在服务器端收到刚才客户端 发送的ACK后,状态从LAST_ACK转换为了CLOSED状态。好的,服务端这边完事了。

客户端:客户端还在TIME_WAIT呢。我们去看看它。

TIME_WAIT状态有两个存在的理由:

可靠地实现TCP全双工连接的终止;

允许老的重复分节在网络中消逝。

那么在Linux下,这个状态要持续60秒,然后客户端的状态变换为CLOSED。

注意:还有同时打开和同时关闭等特殊情况这里没有列举。你现在可以对着小图,在脑海里在 梳理一遍四次挥手的时候客户端和服务器端的状态变换。今天的分享先到这里,后面我们有 文章,重点分析TIME_WAIT状态和调优。

Too many open files

java.net.SocketException: Too many open files
 at java.net.Socket.createImpl(Socket.java:388)
 at java.net.Socket.connect(Socket.java:517)
 at java.net.Socket.connect(Socket.java:469)
 at sun.net.NetworkClient.doConnect(NetworkClient.java:163)
 at sun.net.www.http.HttpClient.openServer(HttpClient.java:394)
 at sun.net.www.http.HttpClient.openServer(HttpClient.java:529)
 at sun.net.www.http.HttpClient.<init>(HttpClient.java:233)
 at sun.net.www.http.HttpClient.New(HttpClient.java:306)
 at sun.net.www.http.HttpClient.New(HttpClient.java:323)
 at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:852)
 at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:793)
 at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:718)
 at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:896)
 at a8.mms.util.Tools.postObject(Tools.java:301) 情景描述:系统产生大量“Too many open files” 
原因分析:在服务器与客户端通信过程中,因服务器发生了socket未关导致的closed_wait发生,致使监听port打开的句柄数到了1024个,
且均处于close_wait的状态,最终造成配置的port被占满出现“Too many open files”,无法再进行通信。 
close_wait状态出现的原因是被动关闭方未关闭socket造成,如附件图所示: 

解决办法:有两种措施可行 
一、解决: 
原因是因为调用ServerSocket类的accept()方法和Socket输入流的read()方法时会引起线程阻塞,所以应该用setSoTimeout()方法设置
超时(缺省的设置是0,即超时永远不会发生);超时的判断是累计式的,一次设置后,每次调用引起的阻塞时间都从该值中扣除,直至另一次
超时设置或有超时异常抛出。 
比如,某种服务需要三次调用read(),超时设置为1分钟,那么如果某次服务三次read()调用的总时间超过1分钟就会有异常抛出,如果要在同
一个Socket上反复进行这种服务,就要在每次服务之前设置一次超时。 
二、规避: 
调整系统参数,包括句柄相关参数和TCP/IP的参数; 

注意: 
/proc/sys/fs/file-max 是整个系统可以打开的文件数的限制,由sysctl.conf控制; 
ulimit修改的是当前shell和它的子进程可以打开的文件数的限制,由limits.conf控制; 
lsof是列出系统所占用的资源,但是这些资源不一定会占用打开文件号的;比如:共享内存,信号量,消息队列,内存映射等,虽然占用了这些资源,
但不占用打开文件号; 
因此,需要调整的是当前用户的子进程打开的文件数的限制,即limits.conf文件的配置; 
如果cat /proc/sys/fs/file-max值为65536或甚至更大,不需要修改该值; 
若ulimit -a ;其open files参数的值小于4096(默认是1024), 则采用如下方法修改open files参数值为8192;方法如下: 
1.使用root登陆,修改文件/etc/security/limits.conf 
vi /etc/security/limits.conf 添加 
xxx - nofile 8192 
xxx 是一个用户,如果是想所有用户生效的话换成 * ,设置的数值与硬件配置有关,别设置太大了。 
#<domain>      <type>     <item>         <value> 

*         soft    nofile    8192 
*         hard    nofile    8192 

#所有的用户每个进程可以使用8192个文件描述符。 
2.使这些限制生效 
确定文件/etc/pam.d/login/etc/pam.d/sshd包含如下行: 
session required pam_limits.so 
然后用户重新登陆一下即可生效。 
3. 在bash下可以使用ulimit -a 参看是否已经修改: 

一、 修改方法:(暂时生效,重新启动服务器后,会还原成默认值) 
sysctl -w net.ipv4.tcp_keepalive_time=600   
sysctl -w net.ipv4.tcp_keepalive_probes=2 
sysctl -w net.ipv4.tcp_keepalive_intvl=2 

注意:Linux的内核参数调整的是否合理要注意观察,看业务高峰时候效果如何。 

二、 若做如上修改后,可起作用;则做如下修改以便永久生效。 
vi /etc/sysctl.conf 

若配置文件中不存在如下信息,则添加: 
net.ipv4.tcp_keepalive_time = 1800 
net.ipv4.tcp_keepalive_probes = 3 
net.ipv4.tcp_keepalive_intvl = 15 

编辑完 /etc/sysctl.conf,要重启network 才会生效 
/etc/rc.d/init.d/network restart 
然后,执行sysctl命令使修改生效,基本上就算完成了。 

------------------------------------------------------------ 
修改原因: 

当客户端因为某种原因先于服务端发出了FIN信号,就会导致服务端被动关闭,若服务端不主动关闭socketFINClient,此时服务端Socket
会处于CLOSE_WAIT状态(而不是LAST_ACK状态)。通常来说,一个CLOSE_WAIT会维持至少2个小时的时间(系统默认超时时间的是7200秒,
也就是2小时)。如果服务端程序因某个原因导致系统造成一堆CLOSE_WAIT消耗资源,那么通常是等不到释放那一刻,系统就已崩溃。因此,
解决这个问题的方法还可以通过修改TCP/IP的参数来缩短这个时间,于是修改tcp_keepalive_*系列参数: 
tcp_keepalive_time/proc/sys/net/ipv4/tcp_keepalive_time 
INTEGER,默认值是7200(2小时)keepalive打开的情况下,TCP发送keepalive消息的频率。建议修改值为1800秒。 

tcp_keepalive_probesINTEGER 
/proc/sys/net/ipv4/tcp_keepalive_probes 
INTEGER,默认值是9 
TCP发送keepalive探测以确定该连接已经断开的次数。(注意:保持连接仅在SO_KEEPALIVE套接字选项被打开是才发送.次数默认不需要修改,
当然根据情形也可以适当地缩短此值.设置为5比较合适) 

tcp_keepalive_intvlINTEGER 
/proc/sys/net/ipv4/tcp_keepalive_intvl 
INTEGER,默认值为75 
当探测没有确认时,重新发送探测的频度。探测消息发送的频率(在认定连接失效之前,发送多少个TCPkeepalive探测包)。
乘以tcp_keepalive_probes就得到对于从开始探测以来没有响应的连接杀除的时间。默认值为75秒,也就是没有活动的连接将在
大约11分钟以后将被丢弃。(对于普通应用来说,这个值有一些偏大,可以根据需要改小.特别是web类服务器需要改小该值,15是个比较合适的值) 

【检测办法】 
1. 系统不再出现“Too many open files”报错现象。 

2. 处于TIME_WAIT状态的sockets不会激长。 

在 Linux 上可用以下语句看了一下服务器的TCP状态(连接状态数量统计)netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 

返回结果范例如下: 

ESTABLISHED 1423 
FIN_WAIT1 1 
FIN_WAIT2 262 
SYN_SENT 1 
TIME_WAIT 962 ulimit -a  指令可查询系统的文件等限制设置数值

time_wait过多

vi /etc/sysctl.conf
编辑/etc/sysctl.conf文件,增加三行:
引用
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
说明:
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,
默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
再执行以下命令,让修改结果立即生效:
/sbin/sysctl -p

last_ack过多

netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c

  6 CLOSE\_WAIT 

  7 CLOSING     
6838 ESTABLISHED

1037 FIN_WAIT1

357 FIN\_WAIT2   
5830 LAST_ACK

  2 LISTEN      

276 SYN\_RECV    

 71 TIME\_WAIT   
[root@ccsafe ~]#

看看系统状态性能都花在系统中断和上下文切换

[root@ccsafe ~]# vmstat 2

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------

r b swpd free buff cache si so bi bo in cs us sy id wa st

1 0 0 3091812 363032 284132 0 0 0 0 1 1 0 0 100 0 0

0 0 0 3091812 363032 284132 0 0 0 0 13750 3174 0 5 94 0 0

0 0 0 3091936 363032 284132 0 0 0 0 13666 3057 1 5 94 0 0

0 0 0 3092060 363032 284132 0 0 0 16 13749 3030 0 5 95 0 0

0 0 0 3092060 363032 284132 0 0 0 0 13822 3144 0 5 95 0 0

0 0 0 3092060 363032 284132 0 0 0 0 13390 2961 0 5 95 0 0

0 0 0 3092060 363032 284132 0 0 0 0 13541 3182 0 6 94 0 0

查看socket队列信息

[root@ccsafe ~]# sar -n SOCK 5

Linux 2.6.18-53.1.13.el5PAE (ccsafe) 10/21/2008

06:31:43 PM totsck tcpsck udpsck rawsck ip-frag tcp-tw

06:31:48 PM 6951 13868 1 0 0 430

Average: 6951 13868 1 0 0 430

根据TCP状态的变化过程来分析LAST_ACK属于被动关闭连接过程中的状态

ESTABLISHED->CLOSE_WAIT->发送ACK->LAST_ACK->(发送FIN+接收ACK)->CLOSED

现在状态都堆积到LAST_ACK初步判断问题从上下两个状态着手

调节一下LAST_ACK时间...

[root@ccsafe ~]# sysctl -a |grep last_ack

net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack = 30

[root@ccsafe ~]# sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack=10

net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack = 10

[root@ccsafe ~]# sysctl -p

[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 5.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  6 CLOSE\_WAIT

  9 CLOSING
6420 ESTABLISHED

693 FIN\_WAIT1

391 FIN\_WAIT2
5081 LAST_ACK

  2 LISTEN

203 SYN\_RECV

 66 TIME\_WAIT
检查一下LAST_ACK所对应的应用

[root@ccsafe ~]# netstat -ant|fgrep "LAST_ACK"|cut -b 49-75|cut -d ":" -f1|sort |uniq -c|sort -nr --key=1,7|head -5

101 220.160.210.6

 46 222.75.65.69

 31 221.0.91.118

 24 222.210.8.160

 22 60.161.81.28
[root@ccsafe ~]#

[root@ccsafe ~]# netstat -an|grep "220.160.210.6"

tcp 0 17280 10.1.1.145:80 220.160.210.6:52787 ESTABLISHED

tcp 1 14401 10.1.1.145:80 220.160.210.6:52513 LAST_ACK

tcp 1 14401 10.1.1.145:80 220.160.210.6:52769 LAST_ACK

tcp 1 14401 10.1.1.145:80 220.160.210.6:52768 LAST_ACK

tcp 0 8184 10.1.1.145:80 220.160.210.6:52515 LAST_ACK

tcp 1 14401 10.1.1.145:80 220.160.210.6:52514 LAST_ACK

tcp 0 8184 10.1.1.145:80 220.160.210.6:52781 LAST_ACK

是TCP80端口的应用调节一下nginx的keepalive时间...

[root@ccsafe ~]# /usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf

2008/10/21 19:15:31 [info] 21352#0: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok

2008/10/21 19:15:31 [info] 21352#0: the configuration file /usr/local/nginx/conf/nginx.conf was tested successfully

[root@ccsafe ~]# ps aux|egrep '(PID|nginx)'

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

root 8290 0.0 0.0 7572 1124 ? Ss Oct04 0:00 nginx: master process /usr/local/nginx/sbin/nginx

nobody 8291 0.2 0.3 19704 13776 ? S Oct04 71:35 nginx: worker process

nobody 8292 0.3 0.2 17604 11680 ? S Oct04 77:26 nginx: worker process

nobody 8293 0.2 0.4 22528 16636 ? S Oct04 58:13 nginx: worker process

nobody 8294 0.3 0.4 24944 19020 ? S Oct04 94:07 nginx: worker process

nobody 8295 0.3 0.5 27496 21508 ? S Oct04 84:41 nginx: worker process

nobody 8296 0.3 0.1 13388 7496 ? S Oct04 84:14 nginx: worker process

nobody 8297 0.2 0.0 9196 3268 ? S Oct04 58:21 nginx: worker process

nobody 8298 0.3 0.2 15392 9504 ? S Oct04 75:16 nginx: worker process

root 21354 0.0 0.0 3896 720 pts/0 S+ 19:15 0:00 egrep (PID|nginx)

动态加载新配置

[root@ccsafe ~]# kill -HUP 8290

[root@ccsafe ~]#

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90 |sort |uniq -c

  1 CLOSE\_WAIT
1138 CLOSING

7161 ESTABLISHED

1427 FIN_WAIT1

396 FIN\_WAIT2
5740 LAST_ACK

  2 LISTEN

350 SYN\_RECV

148 TIME\_WAIT
...

[root@ccsafe ~]# netstat -ant|fgrep ":"|cut -b 77-90 |sort |uniq -c

1151 CLOSING

8506 ESTABLISHED

1452 FIN_WAIT1

666 FIN\_WAIT2   
6568 LAST_ACK

  2 LISTEN      

429 SYN\_RECV    

 92 TIME\_WAIT   
...

LAST_ACK不下而且CLOSING 和FIN_WAIT突增

着重看看可影响主动断开TCP连接时几个参数

tcp_keepalive_intvl:探测消息发送的频率

tcp_keepalive_probes:TCP发送keepalive探测以确定该连接已经断开的次数

tcp_keepalive_time:当keepalive打开的情况下TCP发送keepalive消息的频率

[root@ccsafe ~]# sysctl -a|grep tcp_keepalive

net.ipv4.tcp_keepalive_intvl = 30

net.ipv4.tcp_keepalive_probes = 2

net.ipv4.tcp_keepalive_time = 160

tcp_retries2:在丢弃激活(已建立通讯状况)的TCP连接之前需要进行多少次重试

[root@ccsafe ~]# sysctl -a |grep tcp_retries

net.ipv4.tcp_retries2 = 15

net.ipv4.tcp_retries1 = 3

加速处理那些等待ACK的LAST_ACK减少等待ACK的LAST_ACK的重试次数

[root@ccsafe ~]# sysctl -w net.ipv4.tcp_retries2=5

net.ipv4.tcp_retries2 = 5

减少keepalive发送的频率

[root@ccsafe ~]# sysctl -w net.ipv4.tcp_keepalive_intvl=15

net.ipv4.tcp_keepalive_intvl = 15

[root@ccsafe ~]# sysctl -p

排除syncookies的影响

[root@ccsafe ~]# !ec

echo "0" >/proc/sys/net/ipv4/tcp_syncookies

[root@ccsafe ~]# echo "1" >/proc/sys/net/ipv4/tcp_syncookies

[root@ccsafe ~]# sysctl -a|grep tcp_keepalive

net.ipv4.tcp_keepalive_intvl = 30

net.ipv4.tcp_keepalive_probes = 2

net.ipv4.tcp_keepalive_time = 160

[root@ccsafe ~]# sysctl -a|grep syncookies

net.ipv4.tcp_syncookies = 1

延长keepalive检测周期保留ESTABLISHED数量

[root@ccsafe ~]# echo "1800" >/proc/sys/net/ipv4/tcp_keepalive_time

[root@ccsafe ~]# echo "5" >/proc/sys/net/ipv4/tcp_keepalive_probes

[root@ccsafe ~]# echo "15" >/proc/sys/net/ipv4/tcp_keepalive_intvl

[root@ccsafe ~]# sysctl -a|grep tcp_keepalive

net.ipv4.tcp_keepalive_intvl = 15

net.ipv4.tcp_keepalive_probes = 5

net.ipv4.tcp_keepalive_time = 1800

[root@ccsafe ~]# !wat

watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  1 CLOSE\_WAIT

363 CLOSING
5145 ESTABLISHED

1073 FIN_WAIT1

174 FIN\_WAIT2
6042 LAST_ACK

  2 LISTEN

301 SYN\_RECV

 85 TIME\_WAIT
LAST_ACK不下但是CLOSING有所回落

tcp_orphan_retries:在近端丢弃TCP连接之前要进行多少次重试

[root@ccsafe ~]# sysctl -a|grep tcp_orphan

net.ipv4.tcp_orphan_retries = 0

关键丢TCP太频繁了以至于后勤都跟不上设置丢弃之前的重试次数

[root@ccsafe ~]# echo "3" >/proc/sys/net/ipv4/tcp_orphan_retries

[root@ccsafe ~]# !wat

watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  1 CLOSE\_WAIT

 24 CLOSING
5422 ESTABLISHED

279 FIN\_WAIT1

214 FIN\_WAIT2
1966 LAST_ACK

  2 LISTEN

269 SYN\_RECV

 74 TIME\_WAIT
上下调节该值找个合适的临界点

[root@ccsafe ~]# echo "7" >/proc/sys/net/ipv4/tcp_orphan_retries

[root@ccsafe ~]# !wat

watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  1 CLOSE\_WAIT

175 CLOSING
5373 ESTABLISHED

436 FIN\_WAIT1

209 FIN\_WAIT2
3184 LAST_ACK

  2 LISTEN

283 SYN\_RECV

110 TIME\_WAIT
恢复同时FIN_WAIT1的值过高考虑减少tcp_fin_timeout时间

[root@ccsafe ~]# echo "2" >/proc/sys/net/ipv4/tcp_orphan_retries

[root@ccsafe ~]# sysctl -a|grep tcp_fin

net.ipv4.tcp_fin_timeout = 10

[root@ccsafe ~]# echo "5" >/proc/sys/net/ipv4/tcp_fin_timeout

[root@ccsafe ~]# !wat

watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  2 CLOSE\_WAIT

 17 CLOSING
5665 ESTABLISHED

145 FIN\_WAIT1

141 FIN\_WAIT2
1068 LAST_ACK

  2 LISTEN

287 SYN\_RECV

 68 TIME\_WAIT
相比FIN_WAITSYN_RECV的值偏高加大发送synack的质量

[root@ccsafe ~]# sysctl -a|grep synack

net.ipv4.tcp_synack_retries = 1

[root@ccsafe ~]# echo "2" >/proc/sys/net/ipv4/tcp_synack_retries

[root@ccsafe ~]# !wat

watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  3 CLOSE\_WAIT

 16 CLOSING
5317 ESTABLISHED

200 FIN\_WAIT1

158 FIN\_WAIT2
1001 LAST_ACK

  2 LISTEN

303 SYN\_RECV

 78 TIME\_WAIT
[root@ccsafe ~]# sysctl -a|grep keepalive

net.ipv4.tcp_keepalive_intvl = 15

net.ipv4.tcp_keepalive_probes = 5

net.ipv4.tcp_keepalive_time = 1800

[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  1 CLOSE\_WAIT

  7 CLOSING
5356 ESTABLISHED

175 FIN\_WAIT1

136 FIN\_WAIT2
1045 LAST_ACK

  2 LISTEN

345 SYN\_RECV

 64 TIME\_WAIT
减少keepalive的检测周期LAST_ACK上升

[root@ccsafe ~]# echo "10" >/proc/sys/net/ipv4/tcp_keepalive_intvl

[root@ccsafe ~]# echo "1" >/proc/sys/net/ipv4/tcp_synack_retries

[root@ccsafe ~]# !wat

watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  1 CLOSE\_WAIT

 13 CLOSING
5605 ESTABLISHED

212 FIN\_WAIT1

131 FIN\_WAIT2
1143 LAST_ACK

  2 LISTEN

252 SYN\_RECV

 79 TIME\_WAIT
恢复

[root@ccsafe ~]# echo "15" >/proc/sys/net/ipv4/tcp_keepalive_intvl

[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  3 CLOSE\_WAIT

 14 CLOSING
5862 ESTABLISHED

230 FIN\_WAIT1

205 FIN\_WAIT2
1064 LAST_ACK

  2 LISTEN

244 SYN\_RECV

 59 TIME\_WAIT
[root@ccsafe ~]# watch -n 10 "netstat -ant|fgrep ":"|cut -b 77-90|sort |uniq -c"

Every 10.0s: netstat -ant|fgrep :|cut -b 77-90|sort |uniq -c

  3 CLOSE\_WAIT

 26 CLOSING
6712 ESTABLISHED

270 FIN\_WAIT1

230 FIN\_WAIT2

994 LAST\_ACK

  2 LISTEN

254 SYN\_RECV

 73 TIME\_WAIT
[root@ccsafe ~]#

目前LAST_ACK占ESTABLISHED的量在15%左右

close_wait过多

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
LAST_ACK 1
SYN_RECV 15
CLOSE_WAIT 7729
ESTABLISHED 471
FIN_WAIT1 3
FIN_WAIT2 52
SYN_SENT 1
TIME_WAIT 725
要解决这个问题的可以修改系统的参数,系统默认超时时间的是7200秒,也就是2小时。
默认如下:
tcp_keepalive_time = 7200 seconds (2 hours)
tcp_keepalive_probes = 9
tcp_keepalive_intvl = 75 seconds
意思是如果某个TCP连接在idle 2个小时后,内核才发起probe.如果probe 9(每次75)不成功,内核才彻底放弃,认为该连接已失效
修改后
sysctl -w net.ipv4.tcp_keepalive_time=30
sysctl -w net.ipv4.tcp_keepalive_probes=2
sysctl -w net.ipv4.tcp_keepalive_intvl=2
经过这个修改后,服务器会在短时间里回收没有关闭的tcp连接

find_wait1过多

fin_wait1过多
net.ipv4.tcp_orphan_retries = 0
默认值是7,在近端丢弃TCP链接前,要进行多少次重试,默认值为7个,相当于50-16分钟,视RTO而定。
如果系统是负载很大的web服务器,那么也许需要降低该值,这类sockets可能会耗费大量的资源。