最近某个项目日志中经常报错“socket: too many open files”,导致程序不能正常使用,具体类似如下报错:
10:32:18.043725 ......err=Post "http://172.31.16.249:9074/trade/api/v1/getSpotEntrustList": dial tcp 172.31.16.249:9074: socket: too many open files
10:32:18.043743 ......err:="http://172.31.16.249:9074/trade/api/v1/getSpotEntrustList": dial tcp 172.31.16.249:9074: socket: too many open files
10:32:18.064471 ......err=Post "http://172.31.16.249:9074/trade/api/v1/getSpotEntrustList": dial tcp 172.31.16.249:9074: socket: too many open files
“too many open files”这个错误大家经常会遇到,因为这个是Linux系统中常见的错误,也是云服务器中经常会出现的,而网上的大部分文章都是简单修改一下打开文件数的限制,根本就没有彻底的解决问题。
正如错误信息的字面意思,该错误就是打开了过多文件,系统无法继续打开文件句柄了。这里的file更准确的意思文件句柄(file handle),出现这个报错的大多数情况都是文件句柄(file handle)泄露,通俗的说就是文件句柄在不断的被打开,但是在使用完成之后却没有正常的关闭导致文件打开数不断的增加。
文件句柄泄露有多种原因,而不仅仅是打开文件,常见的来源有:套接字,管道,数据库连接,文件。正常情况下服务器本身是不会突然报这个错误的,一定是我们部署到云服务器上面的业务程序打开了太多文件没有关闭导致同时打开的文件数超出了系统的限制:
我们就需要使用lsof命令进行排查,下面就lsof相关信息做一个基本的介绍:
文件描述符:fd(file descriptor),在Linux系统中一切皆可以看成是文件,文件描述符是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。
linux命令lsof(list system open files):列出系统打开的文件,在终端下输入lsof即可显示系统打开的文件。lsof各个字段的含义:
# lsof
COMMAND PID TID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root cwd DIR 259,2 256 64 /
systemd 1 root rtd DIR 259,2 256 64 /
systemd 1 root txt REG 259,2 1632960 4200757 /usr/lib/systemd/systemd
cpay_api 11103 11110 root 593w REG 259,0 50213 5506167 /opt/logs/cpay_api/WARNING.log
COMMAND:程序的名称
PID:进程标识符
TID:线程标识符
USER:进程所有者
FD:文件描述符
TYPE:文件类型
DEVICE:设备编号
SIZE/OFF:文件的大小
NODE:索引节点
NAME:打开文件的确切名称
FD 列中的常见内容有 cwd、rtd、txt、mem 和一些数字等等。
其中 cwd 表示当前的工作目录;rtd 表示根目录;txt 表示程序的可执行文件;mem 表示内存映射文件。
一般文件句柄打开的FD都是数字开头的,都是被业务程序正在使用的,比如"0u","1u","2u"。
下面这个命令可以看到当前进程文件打开数的数量排序,第一列是打开文件数,第二列是pid,由于lsof的结果会包含线程和系统默认类型的FD,和实际的FD打开数区别较大,所以还需要根据这个命令的排序拿对应的pid再去排查真实的FD打开数量:
# lsof -n |awk '{print $2}'|sort|uniq -c|sort -nr|more
6816 11103
455 2132
371 2120
234 555
225 18907
上面的结果虽然和实际有区别,但是排序基本上是比较一致的,然后再使用下面这个命令查看对应pid进程真实打开的fd数量,看看是否过高,通常来说超过1000就算过高了,如果定位到了具体的进程,然后就要检查对应的程序了。
# lsof -p 11103| awk '{print $4}' |grep "^[0-9]" |wc -l
853
可以看到实际进程pid 11103 的打开FD数为853,那6816个计数是因为有很多线程都共用了这853个打开的文件,所以最后计算的结果与实际的结果区别较大。我们还可以根据 ls /proc//fd 这个命令来确认一下最终的结果:
# ll /proc/11103/fd|wc -l
859
可以看到进程 11103 目录下就是只打开了859个fd,属于正常的打开文件数量。另外,硬盘文件删除后没有释放磁盘空间也是这个原因,因为删除文件的文件句柄未关闭,也可以使用上面的方法去排查。
通过命令 ulimit -a 可以查看当前系统设置的最大句柄数是多少(open files 字段)
# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14113
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 65535
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
可以看到,open files的配置是1024,以下两种方法可以提高用户的系统打开文件数限制
ulimit -n 65535
这种修改方式可以临时把文件打开数量增加到65535,但是系统重启或用户退出后这个配置会失效。
还有一种方式是修改系统的配置文件,以Centos为例,配置文件默认在
/etc/security/limits.conf
在这个配置文件中增加
* soft nofile 65535
* hard nofile 65535
第一列为用用户名,*表示所有用户。 * - nofile 65535 是最简单的全局设置。
nproc是代表最大进程数,nofile 是代表最大文件打开数
在设定上,通常soft会比hard小,举例来说,soft可以设置为80,而hard设定为100,那么你可以使用到90(没有超过100),但介于80~100之间时,系统会有警告信息通知你
如果我们的服务进程是用supervisord进行配置管理的,并且当我们使用以上操作增加了整个系统的限制,但是,如果我们检查对该流程实施的实际限制,则会发现以下内容。
# cat /proc/9675/limits
Limit Soft Limit Hard Limit Units
......
Max open files 1024 4096 files
Max locked memory 65536 65536 bytes
......
尽管该进程可以在我们的 limits.d 目录中打开的文件数量有所增加,但该进程的软限制为 1024 个文件,硬限制为 4096 。
原因是它 supervisord 具有自己的设置,minfds 用于设置可以打开的文件数量。而且该设置会被所有 supervisord 生成的子代继承,因此它会覆盖您可能在中设置的任何设置 limits.d。
它的默认值设置为 1024,可以增加,以便您想要(或需要)任何东西。
# vim /etc/supervisord.conf
[supervisord]
...
minfds = 65535;
增大supervisord的mindfs值至65535,之后再次用supervisor启动Prometheus。
systemctl restart supervisord
再次查看进程的打开文件数限制,已调整至65535。
# cat /proc/9675/limits
Limit Soft Limit Hard Limit Units
......
Max open files 65535 65535 files
Max locked memory 65536 65536 bytes
......
ulimit -n是1024的意思是由root用户执行的某个进程最多只能打开1024个文件,并非root总共只能打开1024个文件。
ls /proc//fd | wc -l 统计pid对应进程打开的fd数量。
lsof -p | awk '{print $4}' |grep "^[0-9]" |wc -l 这个命令的结果和上面的命令应该是一样大。
lsof -u root 这个命令是查看用户粒度的文件打开信息,lsof的结果包含memory mapped .so-files,这些在原理上并不是一般的应用程序控制的fd,所以通常要把这一部分的过滤掉。
lsof -u root| awk '{print $4}' |grep "^[0-9]" |wc -l 查看用户粒度的fd打开数。
cat /proc/pid/limits 这个文件记录了pid进程的所有的limits值,一般以这个为准。
修改配置提高用户的系统打开文件数限制:
vi /etc/security/limits.conf
root soft nofile 8192
root hard nofile 8192
第一列为用用户名,*表示所有用户。 * - nofile 8192 是最简单的全局设置。
cat /proc/sys/fs/file-max 表示当前内核可以打开的最大的文件句柄数,一般为内存大小(KB)的10%,一般我们不需要主动设置这个值,除非这个值确实较小。
cat /proc/sys/fs/file-nr 第一个数字表示当前系统打开的文件数。第三个数字和cat /proc/sys/fs/file-max结果一样表示当前内核可以打开的最大的文件句柄数。