遇到“Too many open files”报错,很多人的第一反应是去调ulimit -n。但折腾半天发现没用,问题可能出在更底层——系统级别的文件描述符上限已经触顶了。这就像小区总水闸关了,你光把自己家的水龙头拧再大也没用。

这个系统级的总闸门,就是内核参数 fs.file-max。它决定了所有进程能打开的文件描述符总数,是一个硬性的全局天花板,不是单个进程的ulimit能突破的。
怎么查当前系统的 fs.file-max 值
先得搞清楚现状。最直接的命令就两个:
cat /proc/sys/fs/file-maxsysctl fs.file-max
输出就是一个数字,比如1048576。这里有个常见的误区:很多人会去/etc/sysctl.conf里找这个配置。如果之前没手动设置过,这一行根本不存在,系统会用一个基于内存计算出的默认值。所以,看运行时的/proc/sys/下的值,才是最准的。
/proc/sys/fs/file-nr 三个数分别代表什么
只看上限还不够,我们得知道“水平”到哪了。file-nr这个文件就是系统的“水平计”。
执行cat /proc/sys/fs/file-nr,你会看到三个数字,比如124560 0 1048576。
- 第一个数(已分配):系统已经分配出去的文件描述符总数。注意,这里包含了那些已经关闭但还没被内核完全回收的。
- 第二个数(空闲):当前已分配但未被任何进程使用的fd数量。这部分可以快速复用。
- 第三个数(上限):就是
fs.file-max的值,那个硬天花板。
关键来了:当第一个数(已分配)非常接近第三个数(上限)时,就意味着系统级的fd池快耗尽了。这时候,无论你怎么调高单个进程的ulimit,新进程或新网络连接都可能会因为申请不到fd而失败。
为什么改了 limits.conf 还是报错
这是另一个高频踩坑点。如果你的服务是由systemd管理的(现在绝大多数Linux发行版都是),那么传统的/etc/security/limits.conf对它基本无效。systemd服务有自己的资源控制机制。
怎么办呢?必须去修改服务的unit文件。以Nginx或MySQL为例,你需要找到或创建对应的.service文件,在里面显式设置:
[Service]
LimitNOFILE=65535
如果想全局生效,可以修改/etc/systemd/system.conf中的DefaultLimitNOFILE项。但记住,改完任何systemd配置,都必须执行systemctl daemon-reload,然后重启服务,改动才会生效。
另外提个醒:这里设置的硬限制(hard limit),不能超过另一个内核参数/proc/sys/fs/nr_open(默认也是1048576),否则服务可能会静默启动失败。
临时调高 vs 永久生效的关键动作
调整fs.file-max也分临时和永久。
临时生效(重启后失效):
sudo sysctl -w fs.file-max=2097152
立竿见影,适合应急。
永久生效:
1. 写入配置文件。推荐使用/etc/sysctl.d/目录,管理起来更清晰:
echo "fs.file-max = 2097152" | sudo tee -a /etc/sysctl.d/99-file-max.conf
2. 最关键的一步:加载配置。 运行sudo sysctl --system。这个命令会加载包括/etc/sysctl.d/在内的所有配置文件,比只运行sysctl -p /etc/sysctl.conf更全面。
3. 最后,务必验证。重启系统后,再次运行cat /proc/sys/fs/file-max,确认数值是否真的生效了。别只看到配置文件里有就以为万事大吉。
说到底,处理文件描述符限制问题,心里要有张清晰的“三层地图”:最底层是系统全局的fs.file-max,中间是进程级的ulimit,而systemd服务又在外面套了一层自己的“隔离罩”。任何一层触顶,都会导致“Too many open files”。排查时,得从全局到局部,一层层检查,才能精准定位瓶颈所在。
