Too many open files (CLOSE_WAIT过多)的解决方案:修改打开文件数的上限值、调整TCP/IP的参数

2022-08-22 13:06:52 浏览数 (1)

引言

解决思路:修改打开文件数的上限值、调整TCP/IP的参数、代码层面及时主动关闭

另外还需要检查程序操作io的流是否在操作完之后关闭,这才是从最更本上的解决。

I 问题分析

1.1 分析方法

  1. lsof - list open files

lsof 是列出系统所占用的资源,但是这些资源不一定会占用打开文件号;比如:共享内存,信号量,消息队列,内存映射并不占用打开文件号;因此需要用命令ulimit -a查看open files 的最大数(当前用户的子进程打开的文件数限制,即imits.conf 文件配置信息)。

  1. 用命令ulimit -a查看open files 的最大数
代码语言:javascript复制
 
[root@test security]# ulimit -a
core file size        (blocks, -c) 0
data seg size         (kbytes, -d) unlimited
file size             (blocks, -f) unlimited
max locked memory     (kbytes, -l) unlimited
max memory size       (kbytes, -m) unlimited
open files (-n) 1024
pipe size          (512 bytes, -p) 8
stack size            (kbytes, -s) 8192
cpu time             (seconds, -t) unlimited
max user processes            (-u) 7168
virtual memory        (kbytes, -v) unlimited
[root@test security]#
 
 

通过以上命令,我们可以看到open files 的最大数为1024。

  1. 在 Linux 上可用netstat查看服务器的 TCP 状态(连接状态数量统计):
代码语言:javascript复制
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


1.2 close_wait产生太多原因分析

close_wait 状态出现的原因:客户端要与服务端断开连接,先发一个FIN表示自己要主动断开连接了,服务端会先回一个ACK,这时表示客户端没数据要发了,但有可能服务端数据还没发完,所以要经历一个close_wait,等待服务端数据发送完,再回一个FIN和ACK。

close_wait产生太多原因:被动关闭方没有迁移到Last_ACK状态,也就是被动关闭方没有发送FIN包。

FIN包的底层实现是调用socket的close方法,被动关闭方没有执行close方法,说明服务端socket忙于读写,没有主动关闭 socket ,发 FIN 给 Client,此时服务端 Socket 会处于 CLOSE_WAIT 状态,而不是 LAST_ACK 状态,致使监听 port 打开的句柄数到了 1024 个,且均处于 close_wait 的状态,最终造成配置的port被占满出现 “Too many open files”,无法再进行通信。

第一次挥手(FIN=1,seq=x)

假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包(终止包),表示自己已经没有数据可以发送了,但是仍然可以接受数据。 发送完毕后,客户端进入 FIN_WAIT_1 状态。

第二次挥手(ACK=1,ACKnum=x 1)

服务器端确认客户端的 FIN(终止) 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。 发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

第三次挥手(FIN=1,seq=y)

服务器端准备好关闭连接时,向客户端发送结束连接请求,发送一个FIN终止包,FIN 置为1。 发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

第四次挥手(ACK=1,ACKnum=y 1)

客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。 服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

II CLOSE_WAIT过多的解决方法

2.1 代码层面

代码层面及时主动关闭:

  1. 使用完socket就调用close方法;
  2. socket读控制,当读取的长度为0时(读到结尾),立即close;
  3. 如果read返回-1,出现错误,检查error返回码。如果不是AGAIN,立即close。

INTR(被中断,可以继续读取) WOULDBLOCK(表示当前socket_fd文件描述符是非阻塞的,但是现在被阻塞了) AGAIN(表示现在没有数据稍后重新读取)。

  1. 如果调用 ServerSocket 类的accept()方法和 Socket 输入流的read()方法时引起线程阻塞,应该用 setSoTimeout() 方法设置超时(超时的判断是累计式的)。

缺省的设置是0,即超时永远不会发生。

2.2 调整TCP/IP的参数

  1. tcp_keepalive_time:允许的持续空闲时长(每次正常发送心跳的周期)
  2. tcp_keepalive_probes :在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
  3. tcp_keepalive_intvl:在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。
代码语言:javascript复制
sysctl -w net.ipv4.tcp_keepalive_time=600   
sysctl -w net.ipv4.tcp_keepalive_probes=2 
sysctl -w net.ipv4.tcp_keepalive_intvl=2 

#sysctl 配置与显示在/proc/sys目录中的内核参数: 可用sysctl来设置或重新设置联网功能,如IP转发、IP碎片去除以及源路由检查等。用户只需要编辑/etc/sysctl.conf文件,即可手工或自动执行由sysctl控制的功能。
#    -w   临时改变某个指定参数的值,如


如果临时改变参数后,可起作用,则修改/etc/sysctl.conf使其永久生效。

代码语言:javascript复制
net.ipv4.tcp_keepalive_time = 1800 #默认值是7200(2小时)
net.ipv4.tcp_keepalive_probes = 3 #默认值是9,TCP 发送 keepalive 探测以确定该连接已经断开的次数
net.ipv4.tcp_keepalive_intvl = 15 #默认值为75,探测消息发送的频率

编辑完 /etc/sysctl.conf ,要重启 network 才会生效:/etc/rc.d/init.d/network restart

2.3 调整系统句柄相关参数 :详见本文第三章节

III 修改打开文件数的上限值

/proc/sys/fs/file-max 是整个系统可以打开的文件数的限制,由 sysctl.conf 控制;

如果 cat /proc/sys/fs/file-max 值为 65536 或甚至更大,不需要修改该值;

3.1 临时改变open files 的值

ulimit 修改当前 shell 和它的子进程可以打开的文件数的限制,由 limits.conf 控制;

代码语言:javascript复制
ulimit -n 4096

3.2 永久设置open files 的值

1.使用root登陆, /etc/security/limits.conf 添加如下一行:

代码语言:javascript复制
#<domain>      <type>     <item>         <value> 

*         soft    nofile    8192 
*         hard    nofile    8192 
#* - nofile 8192
#xxx - nofile 8192
#xxx 是一个用户,如果是想所有用户生效的话换成 * 
#设置的数值与硬件配置有关,别设置太大了。

  1. 生效: 确保/etc/pam.d/login /etc/pam.d/sshd 包含session required /lib/security/pam_limits.so ,然后用户重新登陆一下即可生效。
代码语言:javascript复制
session required /lib/security/pam_limits.so

0 人点赞