0%

linux | 文件描述符

近期在使用 python 进行多进程编程,遇到了 too many open files 这个错误,经过分析后,发现是文件描述符的问题。

这里要知道一个概念,在 linux 中,一切都是文件。

先说一下这个情况是怎么发生的。

  • 项目是一个股票分析中间件
  • 通过接收 redis 集群传递过来的数据
  • 将这些股票数据进行分析整合

基于上述的项目要求,我最初的设计架构是这样的

  • 主进程接受数据,并且,为每一个股票代码创建一个独立的股票进程
  • 然后主进程同时创建一个管道,用以将主进程接受到的数据传递给相关的股票进程

但是,在实际的使用过程中,当股票进程创建到 300 个左右的时候,就会出现 too many open files 这个错误。

这里介绍一下 「文件描述符」。

linux 的文件描述符

文件描述符(FD:file descriptors),也可以说是文件句柄,当某个程序打开文件时,内核返回相应的文件描述符,程序为了处理该文件必须引用此描述符。文件描述符是一个正整数,用以标明每一个被进程所打开的文件和 socket。最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应,后面打开的文件依此类推对应 34…… 。

linux 系统对每个用户、进程、或整个系统的可打开文件描述符数量都有一个限制,一般默认为 1024。当我们在系统或应用的日志中碰到 too many open files 错误记录时,这个其实不是说打开的文件过多,而是打开的文件描述符数量已达到了限制,这时就需要增加文件描述符的数量限制了。

获取系统打开的文件描述符数量

[root@localhost ~]# cat /proc/sys/fs/file-nr
1216    0       197787
  • 第一列 1216 :为已分配的 FD 数量
  • 第二列 0 :为已分配但尚未使用的 FD 数量
  • 第三列 197787 :为系统可用的最大 FD 数量

已用

FD 数量=为已分配的 FD 数量 - 为已分配但尚未使用的 FD 数量

注意,这些数值是系统层面的。

获取进程打开的文件描述符数量

[root@localhost ~]# pidof vim
3253
[root@localhost ~]# ll /proc/3253/fd
lrwx------. 1 test test 64  6月  8 18:11 0 -> /dev/pts/0
lrwx------. 1 test test 64  6月  8 18:11 1 -> /dev/pts/0
lrwx------. 1 test test 64  6月  8 18:11 2 -> /dev/pts/0
lrwx------. 1 test test 64  6月  8 18:11 4 -> /home/test/.bash_history.swp

可以看到 vim 进程用了 4FD

更改文件描述符限制

当碰到 too many open files 错误时,就需要增加文件描述符的限制数量,系统的默认文件描述符都比较大,一般来说,只需增加用户或进程的就可以了

用户或进程

[root@localhost ~]# ulimit -n
1024
[root@localhost ~]# ulimit -n 10240
[root@localhost ~]# ulimit -n
10240

注意,使用 ulimit 命令更改后只是在当前会话生效,当退出当前会话重新登录后又会回到默认值 1024,要永久更改可以修改文件 /etc/security/limit.conf,如

[root@localhost ~]#vi /etc/security/limits.conf
    加入 “abc hard nofile 10240”
  • abc:用户名,即允许 test 使用 ulimit 命令更改 FD 限制,最大值不超过 10240,更改后 abc 用户的每一个进程(以 abc 用户运行的进程)可打开的 FD 数量为 10240
  • hard:限制类型,有 soft/hard 两种,达到 soft 限制会在系统的日志(一般为/var/log/messages)里面记录一条告警日志,但不影响使用。hard,达到这个限制,有日志且会影响使用。
  • nofile:限制的内容,nofile - max number of open files
  • 1024 :值

更改后,退出终端重新登录,用 ulimit 看看有没有生效,如果没生效,可以在 abc 用户的.bash_profile 文件加上 ulimit -n 10240 ,以使用户 abc 每次登录时都会将 FD 最大值更改为 10240,如:

[root@localhost ~]#echo "ulimit -n 10240" >> /home/abc/ .bash_profile
10240
[root@localhost ~]# su - abc
[abc@localhost ~]\$ ulimit -n
10240

limit.conf 文件里面本身已有很详细的使用方法,这个下次可以说说。

系统级别

将整个操作系统可以使用的 FD 数量更改为 51200

[root@localhost ~]# echo "51200" > /proc/sys/fs/file-max
[root@localhost ~]# cat /proc/sys/fs/file-nr
1184    0       51200

像这样更改在系统重启后会恢复到默认值,要永久更改可以在 sysctl.conf 文件加上 fs.file-max = 51200

如:

[root@localhost ~]# echo "fs.file-max = 51200" >> /etc/sysctl.conf

获取打开的文件数量

linux 的一切皆为文件,那么如何知道系统/应用打开了哪些或是多少个文件呢?很简单,用 lsof 命令就行了,lsoflist open files 的简写,可列出程序或系统正在使用的文件。

获取整个系统打开的文件数量

[root@localhost ~]# lsof |wc -l
1864

获取某个用户打开的文件数量

[root@localhost ~]# lsof -u test |wc -l
15

获取某个程序打开的文件数量

[root@localhost ~]# pidof vim
3253
[root@localhost ~]# lsof -p 3253 |wc -l
31

上面所示只是用 lsof 来显示已打开的文件数量,lsof 的功能远不止这些,有兴趣可以 man lsof 看一下

解决方案

按照上述的方法,我的检查步骤如下

pidof python

发现,python 程序大概有 300 个左右,然后选取一个 pythonPID 进行

ll /proc/3253/fd | wc -l

发现每个 python 程序中有 几百个。这就让人感到困惑,因为,按照理论上来说,我只是开启了

  • 一个管道
  • 一个进程

而已,怎么一个程序会打开几百个文件描述符呢?

这个问题,目前,我还没有解决,等以后更新吧。

这里可以放一个运行程序,可以运行一下,就会出现上面的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
import time
from multiprocessing import Process, Pipe


def t(pipe1, pipe2):
while 1:
a = pipe1.recv()
print(f"{os.getpid()} {a}", flush=True)


if __name__ == '__main__':
processes = {}
while 1:
for i in range(3000):
pipe1, pipe2 = Pipe(False)
p = Process(target=t, args=(pipe1, pipe2))
p.start()
pipe2.send(i)
time.sleep(0.01)
请我喝杯咖啡吧~