Linux 系统中文件描述符耗尽的排查与限制调整
前几天一个跑在 Debian 上的小服务突然开始报错,日志里密密麻麻全是 Too many open files,接口一半成功一半 500,看着挺玄乎。我一开始还以为是磁盘满了或者哪儿的内存炸了,结果 df -h 和 free -h 都干干净净。后来才反应过来,这是文件描述符(fd)被用光了。这个问题其实不难,但坑在于:限制有好几层,你改了其中一层,未必有用。下面是我自己踩过来的顺序,照着走一遍基本能定位。
先确认当前限制到底是多少
别急着改配置,先看清楚现状,不然容易白忙。
ulimit -n # 当前 shell 的 soft limit
ulimit -Hn # hard limit
如果是某个已经在跑的服务,直接看它的:
cat /proc/<pid>/limits | grep -i "open files"
输出里 Max open files 分 soft 和 hard 两列。很多情况下你会看到 soft 是 1024,hard 是 4096 或者 1048576,这就说明服务确实被卡在 1024 上了。
顺便看一眼全局水位:
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr
file-nr 有三个数字,第一个是系统已分配的 fd 总数,第三个是全局上限。一般来说这个上限是十万甚至百万级的,真正先撞墙的往往是进程级的 nofile,而不是它。不过如果你确实看到第一个数字贴着第三个跑,那就得调 fs.file-max 了。
找出是谁在疯狂占用 fd
光知道"用完了"没用,得知道是谁用完了。
ls /proc/<pid>/fd | wc -l
lsof -p <pid> | wc -l
这两个数字一般是对得上的。然后按类型归个类:
lsof -p <pid> | awk 'NR>1 {print $5}' | sort | uniq -c | sort -rn | head
REG 是普通文件,IPv4/IPv6 是 socket,FIFO 是管道。如果 socket 占了绝大多数,那八成是连接没关,或者在长连接场景下连接数本来就应该很高。如果 REG 特别多,那要怀疑程序一直 open 文件却没 close,或者日志文件被反复打开。
再补两个我觉得挺有用的:
lsof -p <pid> +L1 # 找出已经被删掉但还被进程攥着的文件
ss -s # 系统层面的 socket 汇总
ss -tan state established | wc -l
+L1 那个尤其值得看,有时候日志轮转做了但进程没重开文件,磁盘空间和 fd 会一起被占着,属于典型的多重暴击。
调整限制,从临时到持久
临时救急最快:
ulimit -n 65535
但注意这只对当前 shell 及其子进程生效,已经跑着的服务不受影响,而且退出就没了,只能算止血。
持久化要分两种情况。普通登录会话走 /etc/security/limits.conf:
* soft nofile 65535
* hard nofile 65535
改完得重新登录才生效,因为这是 PAM 在会话建立时读的。但是,如果你的服务是 systemd 拉起来的,limits.conf 对它基本不起作用,systemd 有自己的那一套。
systemd 服务要改 unit 文件:
[Service]
LimitNOFILE=65535
然后:
systemctl daemon-reload
systemctl restart your-service
systemctl show your-service -p LimitNOFILE
如果不想重启,也可以在运行时直接改:
prlimit --pid <pid> --nofile=65535:65535
这个命令挺好用的,我一般拿它做临时验证,确认调大之后问题消失,再去改配置文件落地。
最后是系统级的天花板:
sysctl -w fs.file-max=2097152
echo 'fs.file-max = 2097152' >> /etc/sysctl.conf
不过说实话,这个值现在默认都挺大的,除非你是某类特殊的网关或者代理,一般轮不到它当瓶颈。
有些程序得改自己的配置
这一步容易被忽略。比如 Nginx,光调系统的 nofile 没用,它自己有 worker_rlimit_nofile 和 worker_connections,两者还得互相匹配,不然会提示 worker_connections exceed open file resource limit。再比如一些 Java 服务,连接池的最大连接数配得比 nofile 还大,那限制调上去了也没意义,只是把爆炸时间往后推了推。
还有 inotify 那一类,如果你用了文件监听,那看的是另一个参数:
cat /proc/sys/fs/inotify/max_user_watches
它跟 fd 不是一回事,但报错信息有时候会让人混。
调完之后别急着收工
限制调大只是把天花板抬高,如果是代码里真的在泄漏,早晚还是会撞上。我一般的做法是调完之后挂个观察:
watch -n 5 'ls /proc/<pid>/fd | wc -l'
盯个十几分钟,如果数字一路往上爬不回落,那基本可以确定是泄漏,这时候该回去看代码了,比如异常分支里忘了 close()、defer 没写、连接池借出去没还之类的。
不出问题的话就没有问题了。但真出问题的时候,先别怀疑内核,多半是限制没调对层,或者程序自己没管好手里的句柄。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。