Linux文件句柄耗尽排查:Too many open files 从ulimit到systemd

    发布时间:2026-09-16 12:31 更新时间:2026-09-16 12:31 阅读量:0

    不少站长遇到过这种场景:网站或后端服务刚启动一切正常,跑上几个小时甚至几天,日志里开始刷 Too many open files,接口报 500,重启一下服务又恢复正常,然后过一段时间再次复发。这个现象通常指向两个方向:一是进程能打开的文件句柄上限被耗尽,二是程序本身存在句柄泄漏(打开后没关闭)。排查的关键不是急着调大数字,而是按顺序核对「系统级限制 → 服务管理器限制 → 进程实际句柄数」这三层,先弄清到底卡在哪一层。

    一、先理解三层限制,别一上来就改 limits.conf

    Linux 中对一个进程能打开的文件描述符数量,存在多层约束,最终生效的是其中较小的那个值:

    第一层是内核参数 fs.file-max,它是整个系统所有进程能打开句柄的总量上限,通常默认值很大,除非是超高并发场景,一般不先动它。第二层是登录会话或服务管理器的限制,也就是 ulimit -n 所代表的 nofile(RLIMIT_NOFILE),它又分 soft limit 和 hard limit,普通进程只能把 soft 提到不超过 hard 的值。第三层是进程自己运行时实际占用的句柄数,用 /proc/<pid>/fd 目录计数就能看到。

    很多教程一上来就让人改 /etc/security/limits.conf,但这个文件只对经过 PAM 登录的会话(如 SSH 登录后的 shell)生效。systemd 管理的服务默认不读 limits.conf,而是走 unit 文件里的 LimitNOFILE 或全局的 DefaultLimitNOFILE。这就是为什么很多人改了 limits.conf、重登服务器后 ulimit 看着变大了,服务却依然报错——因为服务根本不是从那个会话启动的。

    二、按顺序核对:从进程实际值倒推限制来源

    排查建议从「现象最直接的一层」入手,也就是先看报错进程当前的上限和实际占用,再往上找是谁设的这个上限。

    先找到目标进程 PID,并查看它当前生效的句柄上限:

    # 假设服务名为 myapp.service,先取主进程 PID
    systemctl show -p MainPID --value myapp.service
    
    

    查看该进程的句柄上限(Max open files 一行)

    cat /proc/<PID>/limits | grep -i "open files"

    统计该进程当前实际打开的句柄数量

    ls /proc/<PID>/fd | wc -l

    如果 ls /proc/<PID>/fd | wc -l 的结果已经贴着 Max open files 的 soft limit,基本可以确认是句柄耗尽。此时再进一步看这些句柄都指向什么,判断是正常业务量增长还是泄漏:

    # 按句柄指向的目标归类统计,找出占用最多的类型
    ls -l /proc/<PID>/fd | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20

    如果输出里大量是 socket:[...],说明连接没被正确关闭,属于典型的连接泄漏,调大上限只是延后爆掉的时间;如果大量是同一个日志文件或 /tmp 下的临时文件,则要看代码或配置里的文件打开逻辑。注意,不同内核版本 ls -l /proc/<PID>/fd 的输出格式略有差异,具体以实际环境为准。

    确认是上限不够之后,再判断这个上限是谁给的。对 systemd 服务,直接查它继承的限制:

    systemctl show myapp.service | grep -i limit
    

    或查看全局默认

    systemctl show | grep -i DefaultLimitNOFILE

    三、对症下药:limits.conf 与 systemd 该怎么改

    如果服务由 systemd 管理,正确做法是在 unit 文件里显式声明,而不是依赖 limits.conf。可以编辑服务自己的 unit 文件,在 [Service] 段落加入:

    [Service]
    LimitNOFILE=65535
    

    改完执行 systemctl daemon-reload,再 systemctl restart myapp.service,然后用前面的 cat /proc/<PID>/limits 复核是否生效。注意 soft 和 hard 可以分别写,例如 LimitNOFILE=65535:65535;只写一个数时,systemd 会把它同时作为 soft 和 hard。具体取值上限还受内核 fs.nr_open 约束,改到超出可能启动失败,建议先小步调整验证。

    如果服务是普通用户通过 SSH 会话手动或脚本启动,那么 limits.conf 才起作用。在 /etc/security/limits.conf 中追加(注意域名/用户名写法与通配符的适用范围):

    # 对指定用户放开句柄上限
    myuser soft nofile 65535
    myuser hard nofile 65535
    
    

    对 www-data 之类的服务账号同样可单独配置

    修改后需要重新登录(或重启相关会话)才会生效。有些发行版还会在 /etc/security/limits.d/ 下放独立的 conf 文件,且加载顺序可能覆盖主文件,排查时别漏看这个目录。另外要记住:limits.conf 对已经在运行的进程无效,改完必须重启对应服务或会话。

    最后补充一点,如果确认是程序泄漏(句柄数随时间单调上涨、重启即回落),那么无论把上限调到多大都只是权宜之计。此时应结合日志和代码定位未关闭的连接或文件,从根因修复;调大上限只是为修复争取时间的临时缓冲。

    小结与下一步

    遇到 Too many open files,推荐的核对顺序是:先用 /proc/<PID>/fd 确认实际占用与上限是否逼近,再用 systemctl show 或 /proc/<PID>/limits 判断限制来自 systemd 还是登录会话,最后才对症修改 LimitNOFILE 或 limits.conf,改完务必重启服务并用命令复核。把「句柄数随时间是否持续上涨」作为区分容量不足与泄漏的分水岭,能让排查少走很多弯路。日常也可以给关键服务加一条定时采集句柄数的监控,在真正报错前就发现异常增长。

    继续阅读

    📑 📅
    Nginx 后端真实 IP 获取:X-Forwarded-For 与 real_ip 配置 2026-09-16
    rsync 增量同步与断点续传实战:exclude、--delete 与限速 2026-09-16
    MySQL 主从延迟排查:Seconds_Behind_Master 忽高忽低怎么定位 2026-09-16
    容器内存超限被 OOM Kill 排查:指标、cgroup 与堆参数对应 2026-09-16
    Nginx 499 状态码排查实战:客户端断连与 upstream 超时的区别 2026-09-16
    PostgreSQL 连接数与内存调优:max_connections 与 shared_buffers 怎么配 2026-09-16
    服务器时间不同步导致证书与定时任务异常:NTP/chrony 校时配置与排查 2026-09-16
    Linux内存被缓存吃满:buff/cache该不该手动释放 2026-09-17
    SSH 连接频繁断开与卡顿排查:心跳、MTU 与 DNS 反解 2026-09-17
    MySQL表空间与ibdata1膨胀处理:独立表空间、碎片整理与磁盘回收 2026-09-17