Linux 系统中 Too many open files 错误的原理和解决方案

1. 错误原理

  • Too many open files 是 Linux 系统中常见的错误,从字面意思上看就是说程序打开的文件数过多,不过这里的 files 不单是文件的意思,也包括打开的通讯链接(比如 socket),管道,正在监听的端口等等,所以有时候也可以叫做句柄(handle),这个错误通常也可以叫做句柄数超出系统限制。引起的原因就是进程在某个时刻打开了超过系统限制的文件数量以及通讯链接数
    • 一种情况是程序本身就需要打开很多的文件句柄,导致打开的文件数大于系统本身的打开文件数限制,这时需要将系统的限制调高
    • 另一种情况是程序存在文件句柄使用完成之后没有正常关闭的情况,通常是网络连接没关闭,文件打开没关闭等等,这时就需要修复程序中的 bug,确保打开的文件最后都会关闭,网络连接也会关闭
    • 硬盘文件删除后没有释放磁盘空间也是这个原因,因为删除文件的文件句柄未关闭
  • 文件描述符:FD(file descriptor),在 Linux 系统中一切皆可以看成是文件,文件描述符是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行 I/O 操作的系统调用都通过文件描述符
    • FD 列中的常见内容有 cwd、rtd、txt、mem 和一些数字等等。其中 cwd 表示当前的工作目录;rtd 表示根目录;txt 表示程序的可执行文件;mem 表示内存映射文件;一般文件句柄打开的 FD 都是数字开头的,比如 “0u”、”1u”、”2u”
  • lsof:list system open files,列出系统打开的文件。lsof 命令各个字段的含义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#程序名  进程标识  线程标识  线程名  进程所有者  文件描述符  文件类型  设备编号  文件大小  索引节点  打开文件的确切名称
COMMAND PID TID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME
agetty 531 root cwd DIR 8,17 4096 2 /
agetty 531 root rtd DIR 8,17 4096 2 /
agetty 531 root txt REG 8,17 64936 787387 /usr/sbin/agetty
agetty 531 root mem REG 8,17 27002 796829 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
agetty 531 root mem REG 8,17 346132 785481 /usr/lib/locale/C.UTF-8/LC_CTYPE
agetty 531 root mem REG 8,17 3041456 789069 /usr/lib/locale/locale-archive
agetty 531 root mem REG 8,17 51696 783813 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
agetty 531 root mem REG 8,17 1901536 783789 /usr/lib/x86_64-linux-gnu/libc-2.31.so
agetty 531 root mem REG 8,17 177928 783767 /usr/lib/x86_64-linux-gnu/ld-2.31.so
agetty 531 root 0u CHR 4,1 0t0 20 /dev/tty1
agetty 531 root 1u CHR 4,1 0t0 20 /dev/tty1
agetty 531 root 2u CHR 4,1 0t0 20 /dev/tty1
agetty 531 root 4r a_inode 0,13 0 9974 inotify

2. 查询命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 查询某个进程已经开启的文件句柄
# lsof的结果会包含线程和系统默认类型的FD,和实际的FD打开数区别较大,但是排序基本上是比较一致的
# 因为有很多线程共用了打开的文件,所以最后计算的结果与实际的结果区别较大
$ lsof -p <pid> | wc -l

# 查看用户粒度的文件打开信息
$ lsof -u root

# 查看所有进程各自打开的文件数,按数量排序(文件数 PID)
$ lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more

# 查看pid进程真实打开的fd数量
$ lsof -p <pid>| awk '{print $4}' |grep "^[0-9]" |wc -l

# 查看pid进程真实打开的fd数量(一般和上面结果一致)
$ ls /proc/<pid>/fd | wc -l

# 查看当前操作系统已经打开的文件总量
$ cat /proc/sys/fs/file-nr
896 0 9223372036854775807 # 已开启 分配但未使用 总限制数

# 查看操作系统允许打开的文件总量限制
$ cat /proc/sys/fs/file-max
9223372036854775807

# 查看进程可以打开的文件数量限制
$ ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15395
max locked memory (kbytes, -l) 497139
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) 15395
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
$ ulimit -n
1024

# 查看进程所有的limits值
$ cat /proc/<pid>/limits

3. 修改命令

1
2
3
4
5
6
7
8
9
10
11
# 限制某个用户的文件开启数量限制(设定的值不能超过/proc/sys/fs/nr_open里的值)
$ vim /etc/security/limits.conf
root soft nofile 65535 # root用户的每个进程可开启最大的文件数(软限制,只警告)
root hard nofile 65535 # root用户的每个进程可开启最大的文件数(硬限制,无法开启多的文件了)
* soft nofile 65535 # 所有用户的每个进程可开启最大的文件数
* hard nofile 65535 # 所有用户的每个进程可开启最大的文件数

# 限制系统文件开启最大数
$ vim /etc/sysctl.conf
fs.file-max = 6815744
$ sysctl -p # 执行生效

参考