第一次遇到"Too many open files"报错时,我盯着Nginx的error.log发呆了半小时。那是个流量突然暴涨的下午,网站开始间歇性502错误,查看日志才发现是文件描述符耗尽了。这就是limits.conf存在的意义——它像交通警察一样,防止某个进程无节制地占用系统资源。
limits.conf位于/etc/security/目录下,它控制着用户和进程能使用的资源上限。最常见的配置项包括:
这个配置文件采用"用户/组 类型 项目 值"的四段式结构。比如下面这行配置:
bash复制* soft nofile 65535
* hard nofile 65535
表示对所有用户(*)设置软硬限制为65535个文件描述符。这里有个关键点:soft限制是警告阈值,hard限制才是真正的天花板。就像手机流量套餐,soft限制相当于80%用量提醒,hard限制则是流量封顶。
要理解limits.conf的配置,得先搞清楚文件描述符(File Descriptor)是什么。Linux有个著名的设计哲学:"一切皆文件"。不仅是普通文档,连网络连接、设备驱动甚至内存区域都被抽象为文件。每次打开一个文件(包括网络socket),内核就会分配一个文件描述符——这本质上是个整数索引。
想象你走进一家大型图书馆:
当Nginx处理10万个并发连接时,就意味着它需要维护10万个文件描述符。如果没有限制,恶意程序可以通过无限创建连接耗尽系统资源——这就是著名的"DDoS攻击"原理。
通过这个命令可以看到系统级限制:
bash复制cat /proc/sys/fs/file-max
典型值可能是794168(我的测试机结果),表示整个系统最多允许的文件描述符总数。而limits.conf控制的是单个进程或用户能占用的份额。
去年优化电商大促系统时,我们遇到了典型的文件描述符瓶颈。当时Tomcat频繁报错,但检查发现limits.conf配置已经是65535。问题出在哪?原来除了limits.conf,还有多个层级需要协调:
正确的调优顺序应该是:
bash复制# 1. 先检查系统级限制
cat /proc/sys/fs/file-max
# 2. 设置用户级限制(/etc/security/limits.conf)
* soft nofile 100000
* hard nofile 100000
# 3. 验证进程限制
ulimit -n
# 4. 调整应用配置
# Nginx示例:
worker_rlimit_nofile 50000;
文件描述符不是越大越好。根据经验,建议值可以这样估算:
code复制总需求 = (单连接内存开销 × 最大并发) + 系统预留
对于8GB内存的Web服务器:
使用这个脚本实时监控使用情况:
bash复制watch -n 1 "cat /proc/sys/fs/file-nr"
输出三个数字分别表示:已分配FD数/空闲FD数/最大FD数
新手最容易踩的坑就是修改limits.conf后不生效。因为Linux的PAM机制只在用户登录时加载这些配置。我有三种验证方案:
bash复制# 修改配置后
vim /etc/security/limits.conf
# 方法1:注销重新登录
logout
# 方法2:重启服务(系统级)
systemctl restart sshd
# 方法3:针对已存在进程
prlimit --pid $PID --nofile=65535:65535
bash复制# 查看当前shell限制
ulimit -n
# 查看具体进程限制
cat /proc/$PID/limits | grep files
# 统计实际使用量
ls -l /proc/$PID/fd | wc -l
为避免重启失效,建议同时设置:
bash复制# 在/etc/sysctl.conf添加
fs.file-max = 200000
# 在/etc/pam.d/common-session添加
session required pam_limits.so
配置不是一劳永逸的。我们搭建了这样的监控体系:
yaml复制# node_exporter配置
- name: fd_usage
rules:
- alert: FDUsageCritical
expr: (node_filefd_allocated / node_filefd_maximum) > 0.8
for: 5m
labels:
severity: critical
bash复制#!/bin/bash
# 找出FD使用最多的前10进程
ps -e -o pid,comm,args | while read pid name args; do
count=$(ls -1 /proc/$pid/fd 2>/dev/null | wc -l)
echo "$count $name ($pid) $args"
done | sort -rn | head -10
用ab工具模拟高并发:
bash复制ab -n 100000 -c 5000 http://test.com/
同时观察:
bash复制watch -d -n 1 "ss -s | grep TCP"
去年处理过一个经典案例:某金融系统每天凌晨3点准时崩溃。排查发现是定时任务导致:
解决方案组合:
bash复制# 1. 短期应急
echo 200000 > /proc/sys/fs/file-max
# 2. 中期修复
修改limits.conf增加nofile限制
# 3. 长期根治
重写脚本增加fd.close()调用
关键教训:不要只看当前使用量,要关注泄漏趋势。用这个命令查看FD增长情况:
bash复制watch -n 60 "date; lsof -n | awk '{print \$2}' | sort | uniq -c | sort -nr | head"
对于超大规模系统,还需要考虑:
bash复制# 增加TCP连接回收速度
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
# 提高端口复用
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
对于容器环境:
bash复制# 在docker run时指定
--ulimit nofile=65535:65535
Nginx配置示例:
nginx复制events {
worker_connections 50000;
use epoll;
multi_accept on;
}
记住黄金法则:先优化应用代码,再调整系统参数。90%的FD耗尽问题都是由于未正确释放资源导致的。