小Cの已经记不起来的博客

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 没写、连接池借出去没还之类的。

不出问题的话就没有问题了。但真出问题的时候,先别怀疑内核,多半是限制没调对层,或者程序自己没管好手里的句柄。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放