Linux 文件句柄 (File Descriptor) 耗尽故障排查实战
一、故障现象
1.1 典型症状
1 2 3 4 5 6 7 8 9 10 11
| ERROR: Too many open files ERROR: Cannot create new socket: Too many open files ERROR: Failed to open log file: EMFILE
$ ls ls: cannot open directory '.': Too many open files
$ cat /var/log/messages cat: /var/log/messages: Too many open files
|
1.2 影响范围
- 应用无法创建新连接(HTTP、数据库、Redis 等)
- 日志无法写入,导致日志丢失
- 无法打开新文件进行读写
- 严重时可导致服务完全不可用
二、故障诊断
2.1 检查系统级文件句柄限制
1 2 3 4 5 6 7 8 9 10 11
| $ cat /proc/sys/fs/file-max 655360
$ cat /proc/sys/fs/file-nr 24576 0 655360
$ lsof | wc -l 24576
|
2.2 检查用户级限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $ ulimit -n 1024
$ ulimit -a core file size (blocks, -c) unlimited data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 63695 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 63695 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
|
2.3 定位高句柄占用进程
1 2 3 4 5 6 7 8 9 10 11 12
| $ lsof -n | awk '{print $2}' | sort | uniq -c | sort -rn | head -20 15234 12345 8765 23456 5432 34567
$ ls -l /proc/12345/fd | wc -l 15234
$ for i in /proc/[0-9]*/fd; do echo $(ls -l $i 2>/dev/null | wc -l) $(dirname $i); done | sort -rn | head -20
|
2.4 分析进程打开的文件类型
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ lsof -p 12345 | head -50
$ lsof -p 12345 | awk '{print $5}' | sort | uniq -c | sort -rn 8234 IPv4 4521 REG 1890 DIR 456 sock 123 FIFO
$ lsof -p 12345 | grep -E "(IPv4|sock|REG)" | head -100
|
2.5 检查文件句柄泄漏
1 2 3 4 5 6 7 8 9
| $ watch -n 1 'ls /proc/12345/fd | wc -l'
$ strace -p 12345 -e trace=open,openat,close -c
$ lsof -p 12345 | grep deleted
|
三、故障处理
3.1 临时应急处理
1 2 3 4 5 6 7 8 9
| $ ulimit -n 65536
$ systemctl restart nginx $ systemctl restart java-app
$ kill -9 12345
|
3.2 永久修改系统限制
修改 /etc/security/limits.conf
1 2 3 4 5 6 7 8 9 10 11 12
| $ vim /etc/security/limits.conf
* soft nofile 65536 * hard nofile 65536 root soft nofile 65536 root hard nofile 65536
appuser soft nofile 65536 appuser hard nofile 65536
|
修改 systemd 服务限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $ systemctl edit nginx
[Service] LimitNOFILE=65536 LimitNOFILESoft=65536
$ vim /etc/systemd/system/nginx.service [Service] LimitNOFILE=65536
$ systemctl daemon-reexec $ systemctl daemon-reload $ systemctl restart nginx
|
修改系统级限制
1 2 3 4 5 6 7 8 9
| $ vim /etc/sysctl.conf
fs.file-max = 2097152 fs.nr_open = 2097152
$ sysctl -p
|
3.3 应用层优化
Nginx 优化
1 2 3 4 5 6 7 8
| worker_rlimit_nofile 65536;
events { worker_connections 65536; multi_accept on; use epoll; }
|
Java 应用优化
1 2 3 4 5 6 7 8 9 10 11 12 13
| java -Xms2g -Xmx2g \ -Djava.util.logging.config.file=logging.properties \ -XX:+HeapDumpOnOutOfMemoryError \ -jar app.jar
try (FileInputStream fis = new FileInputStream("file.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(fis))) { // 使用资源 } catch (IOException e) { // 处理异常 }
|
MySQL 优化
1 2 3 4 5 6 7 8 9
| SHOW VARIABLES LIKE 'open_files_limit'; SHOW STATUS LIKE 'Open_files';
[mysqld] open_files_limit = 65536 max_connections = 1000 table_open_cache = 4000
|
四、预防措施
4.1 监控告警配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
- alert: HighFileDescriptorUsage expr: node_filefd_allocated / node_filefd_maximum > 0.8 for: 5m labels: severity: warning annotations: summary: "文件句柄使用率过高" description: "{{ $labels.instance }} 文件句柄使用率 {{ $value | humanizePercentage }}"
|
4.2 定期巡检脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #!/bin/bash
THRESHOLD=80 LOG_FILE="/var/log/fd_check.log"
FILE_NR=$(cat /proc/sys/fs/file-nr) ALLOCATED=$(echo $FILE_NR | awk '{print $1}') MAX=$(echo $FILE_NR | awk '{print $3}') USAGE=$(awk "BEGIN {printf \"%.2f\", ($ALLOCATED/$MAX)*100}")
echo "$(date) - 文件句柄使用率:${USAGE}%" >> $LOG_FILE
if (( $(echo "$USAGE > $THRESHOLD" | bc -l) )); then echo "WARNING: 文件句柄使用率超过 ${THRESHOLD}%" >> $LOG_FILE echo "文件句柄告警:${USAGE}%" | mail -s "FD Usage Alert" admin@example.com fi
echo "Top 10 进程句柄占用:" >> $LOG_FILE for i in /proc/[0-9]*/fd; do echo $(ls -l $i 2>/dev/null | wc -l) $(dirname $i) done | sort -rn | head -10 >> $LOG_FILE
|
4.3 代码审查要点
五、典型案例
5.1 案例一:日志文件未关闭导致泄漏
现象:Java 应用运行一周后无法创建新连接
排查:
1 2
| $ lsof -p 12345 | grep deleted | wc -l 8500
|
原因:日志滚动时旧文件被删除,但应用仍持有句柄
解决:
1 2 3 4 5 6 7
| FileInputStream fis = new FileInputStream(logFile);
try (FileInputStream fis = new FileInputStream(logFile)) { }
|
5.2 案例二:连接池配置过大
现象:高并发时数据库连接失败
排查:
1 2
| $ lsof -p 12345 | grep MySQL | wc -l 5000
|
原因:连接池最大连接数设置为 5000,超过系统限制
解决:
1 2 3 4 5 6 7
| spring: datasource: hikari: maximum-pool-size: 100 connection-timeout: 30000 idle-timeout: 600000
|
5.3 案例三:Nginx worker_connections 配置不当
现象:高并发时 Nginx 返回 502 错误
排查:
解决:
1 2 3 4 5 6 7 8 9
| echo "* soft nofile 65536" >> /etc/security/limits.conf echo "* hard nofile 65536" >> /etc/security/limits.conf
worker_rlimit_nofile 65536; events { worker_connections 65536; }
|
六、总结
| 检查项 |
命令 |
正常值 |
| 系统文件句柄上限 |
cat /proc/sys/fs/file-max |
> 655360 |
| 用户文件句柄限制 |
ulimit -n |
≥ 65536 |
| 进程句柄使用率 |
lsof -p PID | wc -l |
< 限制的 80% |
| 已分配句柄数 |
cat /proc/sys/fs/file-nr |
< file-max 的 80% |
关键要点:
- 早发现:配置监控告警,使用率达到 80% 时告警
- 准定位:快速定位高占用进程和文件类型
- 快恢复:临时提高限制 + 重启服务
- 根治:修改配置 + 代码优化 + 定期巡检
参考文档: