引言
解决思路:修改打开文件数的上限值、调整TCP/IP的参数、代码层面及时主动关闭
另外还需要检查程序操作io的流是否在操作完之后关闭,这才是从最更本上的解决。
I 问题分析
1.1 分析方法
lsof
- list open files
lsof 是列出系统所占用的资源,但是这些资源不一定会占用打开文件号;比如:共享内存,信号量,消息队列,内存映射并不占用打开文件号;因此需要用命令
ulimit -a
查看open files 的最大数(当前用户的子进程打开的文件数限制,即imits.conf 文件配置信息)。
- 用命令
ulimit -a
查看open files 的最大数
[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。
- 在 Linux 上可用netstat查看服务器的 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
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 代码层面
代码层面及时主动关闭:
- 使用完socket就调用close方法;
- socket读控制,当读取的长度为0时(读到结尾),立即close;
- 如果read返回-1,出现错误,检查error返回码。如果不是AGAIN,立即close。
INTR(被中断,可以继续读取) WOULDBLOCK(表示当前socket_fd文件描述符是非阻塞的,但是现在被阻塞了) AGAIN(表示现在没有数据稍后重新读取)。
- 如果调用 ServerSocket 类的
accept()
方法和 Socket 输入流的read()
方法时引起线程阻塞,应该用setSoTimeout()
方法设置超时(超时的判断是累计式的)。
缺省的设置是0,即超时永远不会发生。
2.2 调整TCP/IP的参数
- tcp_keepalive_time:允许的持续空闲时长(每次正常发送心跳的周期)
- tcp_keepalive_probes :在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
- tcp_keepalive_intvl:在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。
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
使其永久生效。
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 是一个用户,如果是想所有用户生效的话换成 *
#设置的数值与硬件配置有关,别设置太大了。
- 生效: 确保
/etc/pam.d/login
和/etc/pam.d/sshd
包含session required /lib/security/pam_limits.so
,然后用户重新登陆一下即可生效。
session required /lib/security/pam_limits.so