前两天在调查一个"too many open files"的问题,和之前一样,自然而然的用到了lsof,加上一堆漂亮的命令组合来查看哪些程序打开了很多文件。lsof | awk '{print $2}' | uniq -c | sort -rnk1 | head
啪的一敲回车,很顺利的定位到几个docker内运行的Java进程排在最前,分别都是几万的结果,怀疑是不是docker的问题。很不幸的是,这个调查结果是错的。。。
先说真实的情况,后面再展开分析:
真实的元凶,是一个并没有在上面的命令结果中排在最前面的进程,由于编程的bug,不断的打开同样的文件没有关闭,真正的占用了很多fd。
CentOS 7中的lsof是按PID/TID/file的组合显示结果的,上面lsof组合命令显示“打开”了很文件的进程,只是因为进程运行了N个线程,而每个线程都“用到”了M个jar包,并且FD一栏分别为mem和具体fd号都分别显示了一次,就出现了2*N*M——上万条结果。
结论一:
使用lsof查看fd数是不正确的。
尽管网上很多文章教人这么用,但实际上不应该这么做。
这是因为:
lsof的结果包含了并非以fd形式打开的文件,比如用mmap方式访问文件(FD一栏显示为mem),实际并不占用fd。
其中包括了像.so这样的文件。从结果看.jar文件也是以FD为mem和具体fd编号分别打开了一次。CentOS 7的lsof(我这里lsof -v的版本号是4.87)是按PID/TID/file的组合对应一行,不是一行一个fd。同一个进程如果多个线程访问同一个文件通常只需要打开一次、占用一个fd,但在lsof中就显示多行。
如果用lsof -p <pid>,则不按TID显示,结果数少很多。但仍包含了没有使用fd的文件。
结论二:
准确的查看fd使用总数的命令是:
cat /proc/sys/fs/file-nr
或者(结果多的时候运行需要一段时间)
sudo find /proc -print | grep -P '/proc/\d+/fd/'| wc -l
注意如果用sudo ls -l /proc/*/fd/* | wc -l
结果是不对的,比上面的命令返回结果少很多。原因是实际执行是会把*扩充成具体的目录作为参数,而这个参数长度有限制。
查看具体一个进程号的fd数量是:ls -l /proc/<pid>/fd | wc -l
查看哪个进程使用的fd最多(再来一路组合拳):sudo find /proc -print | grep -P '/proc/\d+/fd/'| awk -F '/' '{print $3}' | uniq -c | sort -rn | head
但还要注意上面的命令返回的是系统的fd使用情况,而ulimit的配置是针对单用户的,两者是有区别的。
结论三:
不同版本的lsof输出结果不同。
CentOS 7.3的lsof (我这里是4.87),按PID/TID/file显示。CentOS 6.6的lsof(我这里是4.82),按PID/file显示。结果数相差很大。但lsof -p <pid>的结果是一致的。
这在容器OS版本和宿主机OS版本不同时就需要注意了,在容器里和宿主机上用lsof查看同一进程的结果会很不同,我碰到的就是这种情况。
</br>
之前使用lsof(或lsof -n不解析协议主机名)的相关命令和真实意义:
打开文件总记录数(没太大意义):lsof | wc -l
或lsof -n | wc -l #不解析协议主机名
查看哪些pid使用文件数量最多(其实也没有太大意义):lsof | awk '{print $2}' | uniq -c | sort -rnk1 | head
作者:petergz
链接:https://www.jianshu.com/p/407c2baef92e