首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Too many open files 排查:进程上限、systemd LimitNOFILE 与 fd 泄漏定位

Too many open files 排查:进程上限、systemd LimitNOFILE 与 fd 泄漏定位

原创
作者头像
一只特立独行的兔先生
发布于 2026-11-09 09:39:08
发布于 2026-11-09 09:39:08
10
举报

在云服务器上跑网关、消息消费者或者高并发的 Java/Go 服务,流量一上来日志里就刷出 Too many open files,新连接建不起来,文件也打不开。这个报错看起来简单,但“改了 ulimit 还是不生效”的情况非常多。本文以 CVM 上常见的 systemd 托管服务为例,梳理排查顺序和几个容易踩的坑。

一、先分清是哪一层的上限

Linux 对文件描述符(fd)有两层限制,报错文字也不同:

报错

errno

含义

对应参数

Too many open files

EMFILE

单个进程打开的 fd 超过了它的 RLIMIT_NOFILE

ulimit -n、LimitNOFILE

Too many open files in system

ENFILE

整个系统已分配的 fd 达到上限

fs.file-max

绝大多数线上问题是第一种:进程级上限太低,或者程序本身在泄漏 fd。

二、看进程的真实上限和当前用量

不要在自己的 SSH 会话里执行 ulimit -n 来判断,那只是当前 shell 的值。要看出问题的那个进程:

代码语言:bash
复制
PID=$(pgrep -f my-service | head -1)

# 进程实际生效的软/硬限制
grep "Max open files" /proc/$PID/limits

# 当前已打开的 fd 数量
ls /proc/$PID/fd | wc -l

如果已打开数量贴着软限制,基本可以确定是进程级上限触顶。系统级的情况看 /proc/sys/fs/file-nr,三列分别是已分配数、空闲数(新内核恒为 0)和 fs.file-max:

代码语言:bash
复制
cat /proc/sys/fs/file-nr

三、为什么改了 limits.conf 不生效

/etc/security/limits.conf 由 PAM 的 pam_limits 模块在登录会话时应用,只影响 SSH、su 等通过 PAM 登录启动的进程。由 systemd 启动的服务不经过 PAM,所以这里改得再大,服务进程也看不到。

systemd 服务要在 unit 里配置。推荐用 drop-in,不直接改厂商的 unit 文件:

代码语言:bash
复制
sudo systemctl edit my-service

在打开的编辑器中写入:

代码语言:ini
复制
[Service]
LimitNOFILE=65535

然后重启服务并复核:

代码语言:bash
复制
sudo systemctl restart my-service
grep "Max open files" /proc/$(systemctl show -p MainPID --value my-service)/limits

没有单独配置的服务使用 /etc/systemd/system.conf 里的 DefaultLimitNOFILE。较新的 systemd 上游默认是软限制 1024、硬限制 524288,发行版可能另有修改,以实际查询为准:

代码语言:bash
复制
systemctl show -p DefaultLimitNOFILE -p DefaultLimitNOFILESoft

注意软限制 1024 对高并发服务明显不够,这是“服务器配置很高却报 fd 不足”的常见原因。

四、线上不能重启时的临时处理

prlimit 可以直接修改运行中进程的限制,不需要重启,适合先止血:

代码语言:bash
复制
sudo prlimit --pid $PID --nofile=65535:65535
grep "Max open files" /proc/$PID/limits

这只对当前进程有效,服务重启后恢复原值,事后一定要补上 unit 配置。另外,进程的硬限制不能超过内核参数 fs.nr_open,设置过大会直接失败。

五、上限够了还在涨:排查 fd 泄漏

如果把上限调到 65535 之后,fd 数量仍然持续增长直到再次触顶,问题就不是配置,而是程序没有释放资源。先按类型统计:

代码语言:bash
复制
sudo lsof -p $PID | awk 'NR>1 {print $5}' | sort | uniq -c | sort -rn | head
  • IPv4/IPv6/sock 占大头:多半是连接没关。再看连接状态:
代码语言:bash
复制
sudo ss -tanp | grep "pid=$PID," | awk '{print $1}' | sort | uniq -c

大量 CLOSE-WAIT 说明对端已经关闭,而本进程没有调用 close,常见于 HTTP 客户端响应体没关闭、连接池使用不当。

  • REG(普通文件)占大头:用 ls -l /proc/$PID/fd 看具体指向哪些文件,通常能直接定位到忘记关闭的日志或临时文件。

可以每隔几分钟记录一次 ls /proc/$PID/fd | wc -l,只增不减就是泄漏,和业务高峰同步起落则更可能是正常的并发需求。

六、容器里的服务

Docker 容器内进程的上限默认继承自容器运行时(dockerd/containerd)自身的设置,与宿主机登录会话无关;也可以在 /etc/docker/daemon.json 的 default-ulimits 里统一指定。单个容器需要单独设置时,在启动参数里指定:

代码语言:bash
复制
docker run --ulimit nofile=65535:65535 ...

Kubernetes 中则取决于容器运行时的配置,排查方法相同:进入容器后看 /proc/1/limits。

七、一个容易忽略的程序限制

即使系统上限调高了,使用 select() 的老程序仍然只能处理编号小于 FD_SETSIZE(通常为 1024)的 fd,超过后会出现异常甚至内存越界。遇到这种情况,调整 ulimit 没有用,需要把代码改为 poll 或 epoll。

总结

排查顺序可以归纳为:

  1. 看报错是 EMFILE 还是 ENFILE,确定是进程级还是系统级;
  2. 用 /proc/<pid>/limits 和 /proc/<pid>/fd 看真实上限和用量;
  3. systemd 服务改 LimitNOFILE,不要指望 limits.conf;
  4. 紧急时用 prlimit 止血,事后补配置;
  5. 上限调高后仍持续增长,按 fd 类型和连接状态找泄漏。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、先分清是哪一层的上限
  • 二、看进程的真实上限和当前用量
  • 三、为什么改了 limits.conf 不生效
  • 四、线上不能重启时的临时处理
  • 五、上限够了还在涨:排查 fd 泄漏
  • 六、容器里的服务
  • 七、一个容易忽略的程序限制
  • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档