Linux 资源耗尽 too many open files 定位

业余草

共 5216字,需浏览 11分钟

 ·

2024-07-02 18:10

来源:juejin.cn/post/7356078518096543744

推荐:https://t.zsxq.com/i7NM2

错误原理

too many open files这个错误大家经常会遇到,因为这个是Linux系统中常见的错误,也是云服务器中经常会出现的,而网上的大部分文章都是简单修改一下打开文件数的限制,根本就没有彻底的解决问题。

本文就是帮助开发者理解这个问题的来龙去脉,确保大家读完本文之后能够彻底明白和解决这个问题。首先一定要明确机器为什么会报这个错误,其次才好更加深入的去解决这个问题,正如错误信息的字面意思,就是打开了过多文件,系统无法继续打开文件句柄了。这里的file更准确的意思文件句柄(file handle),出现这个报错的大多数情况都是文件句柄(file handle)泄露,通俗的说就是文件句柄在不断的被打开,但是在使用完成之后却没有正常的关闭导致文件打开数不断的增加。

文件句柄泄露有多种原因,而不仅仅是打开文件,常见的来源有:套接字,管道,数据库连接,文件。正常情况下服务器本身是不会突然报这个错误的,一定是我们部署到云服务器上面的业务程序打开了太多文件没有关闭导致同时打开的文件数超出了系统的限制:

一种情况是程序本身就需要打开很多的文件句柄,这种情况就是打开的文件数大于系统本身的打开文件数限制,这时需要我们将系统的限制调高,后面会给出具体的方法;

另外一种情况就是我们的程序存在文件句柄使用完成之后没有正常的关闭的情况,通常是网络连接没关闭,文件打开没关闭等等,这时就需要我们修复程序中的bug,确保打开的文件最后都会关闭,网络连接也会关闭。

分析问题

为了准确的定位到具体问题在哪里,我们就需要使用lsof命令,下面就lsof相关信息做一个基本的介绍:

文件描述符:fd(file descriptor),在Linux系统中一切皆可以看成是文件,文件描述符是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。

linux命令lsof(list system open files):列出系统打开的文件,在终端下输入lsof即可显示系统打开的文件。lsof各个字段的含义:

COMMAND PID TID USER FD TYPE DEVICE SIZE NODE NAME
程序的名称 进程标识符 线程标识符 进程所有者 文件描述符 文件类型 设备编号 文件的大小(bytes) 索引节点 打开文件的确切名称

FD 列中的常见内容有 cwd、rtd、txt、mem 和一些数字等等。其中 cwd 表示当前的工作目录;rtd 表示根目录;txt 表示程序的可执行文件;mem 表示内存映射文件。所以一般文件句柄打开的FD都是数字开头的, 比如"0u","1u","2u"。

这里我们先使用lsof找出打开文件数较多的进程,然后再挨个排查其打开文件数的具体情况:

下面这个命令可以看到当前进程文件打开数的数量排序,第一列是打开文件数,第二列是pid,由于lsof的结果会包含线程和系统默认类型的FD,和实际的FD打开数区别较大,所以还需要根据这个命令的排序拿对应的pid再去排查真实的FD打开数量:

lsof -n |awk '{print $2}'|sort|uniq -c|sort -nr|more

[root@linux]# lsof -n |awk '{print $2}'|sort|uniq -c|sort -nr|more
   1652 17336
   1376 3577
   1296 17335
   1247 9380
    944 17329
    912 17330
    910 4576
    800 26693
    .........
    more

上面的结果虽然和实际有区别,但是排序基本上是比较一致的,然后再使用下面这个命令查看对应pid进程真实打开的fd数量,看看是否过高,通常来说超过1000就算过高了,如果定位到了具体的进程,然后就要检查对应的程序了。

lsof -p <pid>| awk '{print $4}' |grep "^[0-9]" |wc -l 
[root@linux]# lsof -p 17336| awk '{print $4}' |grep "^[0-9]" |wc -l 
47

可以看到实际进程pid 17336 的打开FD数为47,那1652个计数是因为有很多线程都共用了这47个打开的文件,所以最后计算的结果与实际的结果区别较大。我们还可以根据 ls /proc//fd 这个命令来确认一下最终的结果:

[root@linux]# ls /proc/17336/fd
0  10  12  14  16  18  2   21  23  25  27  29  30  32  34  36  38  4   41  43  45  5  7  9
1  11  13  15  17  19  20  22  24  26  28  3   31  33  35  37  39  40  42  44  46  6  8

可以看到进程17336 目录下就是只打开了47个fd,属于正常的打开文件数量。另外,硬盘文件删除后没有释放磁盘空间也是这个原因,因为删除文件的文件句柄未关闭,也可以使用上面的方法去排查。

命令拓展

ulimit -n是1024的意思是由root用户执行的某个进程最多只能打开1024个文件,并非root总共只能打开1024个文件。

ls /proc/<pid>/fd | wc -l  统计pid对应进程打开的fd数量。

lsof -p <pid>| 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结果一样表示当前内核可以打开的最大的文件句柄数。

根据进程名查看该进程打开的所有文件信息

lsof -c app_name

lsof -c 后跟进程名称,可以查看该进程打开的所有的文件信息,包括普通文件、sock文件、fifo、字符设备文件等。

由fd查看其对应的文件名及打开该文件的进程信息

lsof -d fd

lsof -d 后跟fd,可以打印出系统中所有进程打开的文件描述符为fd的进程及文件信息:

查看某路径下文件正在被哪些进程打开使用

lsof +d path

lsof +d 后跟目录名,查看该目录下的文件(不递归到子目录中)被哪些进程打开:

递归查看打开某目录及其子目录下文件的所有进程信息

lsof +D path

lsof +D 后跟目录,可以查看打开该目录下所有文件及子目录下文件的进程有哪些,比如,查询/var目录下文件被哪些进程打开,可以看到/var/log/下文件及/var/spool/下文件都能查询到:

查看指定pid打开的所有文件信息

lsof -p pid

lsof -p 后跟进程pid,可以按进程pid查看到该进程所打开的所有文件:

查看所有tcp连接数

netstat -n | awk '/^tcp/ {++S[NF]} END {for(a in S) print a, S[a]}'

或者(效果等同)

netstat -n | awk '/^tcp/ {++state\[NF\]} END {for(key in state) print key,"\\t",state\[key\]}'

浏览 92
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报