暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

每日一问题探索-细谈“Too many open files in system”

五分钟学SRE 2021-12-03
2705

    在使用 Linux 服务器时,会遇到“Too many open files in system”错误,出现这个问题大部分是两个原因:

1,这是由于文件句柄配置参数过小,不能满足应用的需求

2,由于进程处理不当,导致文件句柄泄漏。换句话说,即进程打开了文件句柄,但未能正确关闭,导致文件大量使用,直至最终耗尽。(如程序打日志持续打开文件,但是没有close or 没有及时关闭Socket或数据库


首先我们需要先理清楚文件描述符与文件句柄这两个概念

什么是文件描述符?

从常规数据到网络套接字,在 Linux 中一切都是文件!文件描述符是 Linux 中打开文件的非负整数标识符。每个进程都有一个打开的文件描述符表,其中在打开新文件时附加一个新条目。

    内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

tip:这里的文件是广义的:

除了普通文件和目录外,还包括管道、FIFO(命名管道)、Socket、终端、设备等。
复制


    例如,当我们cat一个文件时会发生什么?它打开通过open()系统调用作为参数传递的文件,并为其分配一个文件描述符。然后,它通过文件描述符与文件交互——在这种情况下,只是为了显示它的内容——最后,通过close()系统调用关闭它。

一个进程默认打开三个文件描述符:


0表示stdin,标准输入
1表示stdout,标准输出
2表示stderr,标准错误
复制


文件句柄(file handle)

内核会维护系统内所有打开的文件及其相关的元信息,该结构称为打开文件表(open file table)。表中每个条目包含以下域:

文件的偏移量。POSIX API中的read()/write()/lseek()函数都会修改该值
打开文件时的状态和权限标记。通过open()函数的参数传入
文件的访问模式(只读、只写、读+写等)。通过open()函数的参数传入
指向其对应的inode对象的指针。内核也会维护系统级别的inode表
复制

    文件描述符表、打开文件表、inode表之间的关系可以用《The Linux Programming Interface:A Linux and UNIX System Programming Handbook》书中的下图来表示。

    一个打开的文件可以对应多个文件描述符(不管是同进程还是不同进程),一个inode也可以对应多个打开的文件。打开文件表中的一行称为一条文件描述(file description),也经常称为文件句柄(file handle)。

哪些操作会分配文件句柄:

open系统调用打开文件(path_openat内核函数)
打开一个目录(dentry_open函数)
共享内存attach (do_shmat函数)
socket套接字(sock_alloc_file函数)
管道(create_pipe_files函数)
epoll/inotify/signalfd等功能用到的匿名inode文件系统(anon_inode_getfile函数)
复制


检查打开的文件句柄

1,系统级别的文件句柄数量限制可以查看proc文件

file-nr文件里面的第一个字段代表的是内核分配的struct file的个数,也就是文件句柄个数,而不是文件描述符

linux 内核文档说明

file-nr denote the number of allocated file handles, the numberof allocated but unused file handles, and the maximum number offile handles. Linux 2.6 always reports 0 as the number of freefile handles -- this is not an error, it just means that thenumber of allocated file handles exactly matches the number ofused file handles.

查看当前系统句柄使用情况

复制
# cat /proc/sys/fs/file-nr
39360655350
已分配文件句柄的数目 已分配未使用文件句柄的数目 文件句柄的最大数目
复制

复制

查看系统文件句柄限制

复制
# cat /proc/sys/fs/file-max
655350
进程级别句柄限制
# ulimit -n
655350
复制

表示当前用户、当前终端、单个进程能拥有的文件描述符的数量阈值
复制

2,另外一个常用的命令是lsof

    因为文件描述符和文件句柄是两个不同的东西:

    lsof在用户空间,主要还是从文件描述符的角度来看文件句柄,所以lsof看到的是文件描述符不能代表文件句柄,这就是为什么会经常会看到lsof|wc -l看到的文件句柄数量与 /proc/sys/fs/file-nr 不一致,而且lsof|wc -l的数量还会超过ulimit -n看到的限制


问题的检查

1,文件句柄设置过小

使用cat /proc/sys/fs/file-nr 与ulimit -n查看是否是因为文件句柄设置过小引起

修改文件句柄进程级别句柄

临时修改

ulimit -n 8192
复制


永久修改:

复制
vim /etc/security/limits.conf
#soft是软限制,超过只会告警;hard是硬限制,超过就报错。
#<domain> <type> <item> <value>
# 用户名 软/硬限制 限制项 阈值
root soft nofile 65535
root hard nofile 65535
复制

复制

系统级别句柄

复制
~ vim /etc/sysctl.conf
fs.file-max = 5242880
# 立即生效
~ sysctl -p
复制

复制


2,检查是否存在文件句柄泄漏

1),通过 /proc接口确认有多少文件被当前进程打开


ls /proc/<pid>/fd |wc -l
复制


这个方法速度很快,但是它们不会告诉您实际打开了哪些文件

2),通过lsof查看

复制
查看打开文件描述符最多的top 20
lsof |awk '{ print $2 " " $1; }' | sort -rn | uniq -c | sort -rn | head -20
查看某个进程号打开的所有文件
lsof -p <pid>
查看某个进程名打开的所有文件
lsof -c <COMMAND>
查看某个用户打开的所有文件
lsof -u <USER>
复制

复制

    通过上面命令可以看到哪些进程存在句柄的泄露,以及操作打开了哪些文件

    如果可以单独踢掉机器应用的流量可以先踢掉该机器的流量,然后再进行排查

案例介绍:

    lsof 出现大量的 "can't identify protocol",代表socket泄漏,同时会显示哪个进程使用的sock未关闭

详情可见stack overflow:Seeing too many lsof can't identify protocol

https://stackoverflow.com/questions/7911840/seeing-too-many-lsof-cant-identify-protocol


推荐学习博文:https://blog.csdn.net/nazeniwaresakini/article/details/104220111

推荐学习书籍(《The Linux Programming Interface:A Linux and UNIX System Programming Handbook》):http://index-of.es/OS/The%20Linux%20Programming%20Interface.pdf

推荐查看内核文档(Linux kernel.org):https://www.kernel.org/doc/Documentation/sysctl/fs.txt


文章转载自五分钟学SRE,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论