1. 现象

服务的cpu跑满(golang实现), 并大量报too many open files错误.服务使用systemd来运行,部署在阿里ecs.

 

2.分析

从日志来看,cpu的上升主要为到达文件数限制引起的,但之前已经更改过系统的文件数及所有用户的文件数,按道理是不应该出现这个问题的,后来查阅资料发现,文件数可以从三个维度限制分别为操作系统限制,用户态限制,以及进程限制,对于这三个维度选取最小值生效.于是对系统进行分析.

 

首先查看当前打开文件数, 进程占用的文件数并不多.

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



然后获取系统级文件数限制

输入命令

cat /etc/sysctl.conf

得到

fs.file-max = 1000000

查询用户级文件数限制

cat /etc/security/limits.conf

得到

* soft nofile 655350

* hard nofile 655350

单独获取程序文件数限制(9928为进程id)

cat /proc/9928/limits

得到

Max open files            1024                 4096                 files    

如上可以发现, 虽然系统及用户的文件数调大了,但是程序的仍然是一个很低的值, 这里进程的文件数一般情况下是默认继承用户级的值的,而这里却没有继承,一开始怀疑是systemd启动的问题,但是手写了另外一个测试服务,发现该服务又继承了用户文件数.

百思不得其解的情况下在systemd的启动脚本里加了文件数的初始化值.

 如下:

[Service]

Type=simple

LimitNOFILE=40960

LimitNPROC=40960

  

单独获取程序(9928为进程id)

cat /proc/9928/limits

得到

Max open files            40960                 40960                 files    



发现文件数被设置成了启动时的初始化值.至于为什么没有继承用户级的值,怀疑是程序里做了参数设置,这里如果有人知道golang里具体情况的话,还望不吝赐教.

 

3. 总结

归结来说出现文件描述符的错误的排查步骤如下:

首先,判断配置参数是否正确,这里涉及到对上面提到的三个维度的检查,特别时进程维度的,如果只是ulimit -n 一下就完事了,那估计旧要像我一样进坑了.

如果参数都正确,那么查看一下当前系统被使用了多少文件数,如果使用的确实多,那要看一下使用在什么地方,这里一般有两种情况,大量连接未关闭,或者大量读文件的句柄未关闭.具体原因相信到这里就可以排查出来了.