服务器报 "Too many open files" 崩溃?文件描述符(File Descriptor)耗尽排查与解决
关键词:Too many open files、Errno 24、EMFILE、file descriptor、文件描述符耗尽、 ulimit -n、nofile、limits.conf、LimitNOFILE、docker compose ulimits、高并发服务器崩溃、 accept 报错、lsof -p、/proc/pid/limits
问题背景
高并发 Web 服务在连接数升高后突然崩溃,报 [Errno 24] Too many open files,
即使 内存还有几 GB、磁盘空间充足,也 accept() 直接失败、无法接受新连接。
用 lsof -p <pid> 一看,进程打开的文件描述符(多为 socket)已占满上限。
这是服务器运维里极其经典且高频的问题:文件描述符(File Descriptor)耗尽。
复现步骤
# 用 docker 起一个低 ulimit 环境(模拟默认 1024 限制)
docker compose up -d --build
docker compose exec app bash
# 1) 查看当前 fd 限制
ulimit -n # soft limit(通常是 1024)
ulimit -Hn # hard limit
cat /proc/sys/fs/file-max # 系统级最大 fd
# 2) 打开大量文件触发 EMFILE
python3 fd_exhaust.py
# 输出:❌ 在打开第 1022 个文件时失败:[Errno 24] Too many open files
# 3) 高并发服务器崩溃
python3 server.py 8080 & # 启动服务器
bash stress_test.sh 8080 1500 3 # 并发 1500 个长连接
# 输出:❌ 服务器崩溃![Errno 24] Too many open files,达到文件描述符上限
根本原因
Linux 对每个进程的文件描述符数量做了限制(ulimit -n,默认仅 1024)。
高并发服务需要同时打开成百上千个 socket,一旦超过限制,内核拒绝分配新的 fd,
open() / accept() 就会抛 EMFILE: Too many open files。
关键点:fd 限制与内存、磁盘无关,所以"资源充足但连不上"是典型表象。
解决办法(按场景选一种)
方案 A:docker-compose(本项目采用,永久、声明式)
docker-compose.yml 中给服务加:
ulimits:
nofile:
soft: 65535
hard: 65535
改完 docker compose down && docker compose up -d 生效。
方案 B:临时生效(当前 shell,重启失效)
ulimit -n 65535
方案 C:永久生效(系统 / systemd)
/etc/security/limits.conf或/etc/security/limits.d/*.conf:* soft nofile 65535 * hard nofile 65535- systemd 服务 unit:
[Service] LimitNOFILE=65535 - 运行时:
docker run --ulimit nofile=65535:65535 ...
验证
ulimit -n→ 65535cat /proc/<pid>/limits→ 查看进程实际 Max open filesfd_exhaust.py→ 能打开 6 万+ 个文件(远超 1024)- 压测 3000 并发长连接 → 服务器正常存活,不再崩溃
经验与避坑
- 注意 Docker 版本差异:较新 Docker(v29+)容器默认
nofile就是 65535, 若不显式调低,问题不会复现。复现"经典 1024 限制"要用ulimits: nofile: 1024:1024。 os.getrlimit不存在:Python 取 fd 限制要用resource.getrlimit(resource.RLIMIT_NOFILE)。- 单线程同步 accept + 立即 close 复现不了 fd 耗尽:连接处理完就关、不累积; 需让服务器保持连接打开(模拟长连接/长轮询/慢客户端)才能在进程内累积并发 socket 并触发 EMFILE。
b"..."与 f-string 隐式拼接会报错:SyntaxError: cannot mix bytes and nonbytes literals, 应写成b"..." + f"...".encode()。- 重定向日志看不到输出:
nohup python3 ... > log 2>&1下 stdout 有缓冲, 用print(..., flush=True)保证实时可见。 - 排查命令速查:
ulimit -n、cat /proc/<pid>/limits、lsof -p <pid> | wc -l、cat /proc/sys/fs/file-nr(系统已用/空闲 fd)。
技术栈
Linux、Docker / docker-compose、Python3、Nginx/Node 等 Web 服务、systemd、EMFILE 错误处理。
