linux 环境服务发生文件句柄泄漏导致服务不可用
问题描述:
服务调用远程rest接口 报错,发生too many open files 异常,系统句柄资源耗尽,导致服务不可用。
排查经过:
1、针对报错代码进行本地构建,构造异常,并进行压测。问题未复现
2、经过讨论分析,问题发生根因为:句柄资源耗尽,那么必然存在进程持续消耗句柄资源,且不进行释放。
3、因此,开始排查服务进程句柄信息
# 统计进程句柄总数lsof -p | wc -l # 统计进程目录句柄总数 若持续增加则存在泄漏lsof -p | grep DIR # 统计进程delete文件删除句柄总数 若持续增加则存在泄漏lsof -p | grep delete
通过统计观察各个进程的句柄总数,分析查找到问题根源
原因最终确认:
问题代码:loadDirectory函数加载指定目录下所有文件,并返回Path列表。Files.list函数为安全关闭文件句柄,导致该方法频繁调用时未释放目录句柄资源。导致系统句柄资源耗尽
public static List loadDirectory(String fileDirectory) { try { final Path pathDirectory = Path.of(fileDirectory); if (Files.isDirectory(pathDirectory)) { return Files.list(pathDirectory) .collect(Collectors.toList()); } else { throw new NotDirectoryException(fileDirectory); } } catch (IOException e) { log.error(\"file write error:\", e); } return new ArrayList(0); }
问题修复 :使用try-resource-with 方式主动关闭资源
public static List loadDirectory(String fileDirectory) throws CheckingException { try (Stream stream = Files.list(Paths.get(fileDirectory))) { return stream.collect(Collectors.toList()); } catch (IOException e) { log.error(\"fileDirectory is not directory:\", e); throw new CheckingException(\"can not load directory:\" + fileDirectory); } }
问题复盘:创建系统句柄监控,进程句柄如果超过300,则进行告警,超过1000,则提示高危进程
#!/bin/bash# 高效进程句柄监控脚本 v2.0THRESHOLD=300 # 单个进程句柄阈值HIGH_THRESHOLD=2000 # 高危进程阈值INTERVAL=20 # 监控间隔(秒)LOG_DIR=\"logs\"LOG_FILE=\"${LOG_DIR}/handle_monitor_$(date +%Y%m%d).log\"MAX_LOG_DAYS=30 # 日志保留天数# 创建日志目录mkdir -p ${LOG_DIR}# 清理旧日志find ${LOG_DIR} -name \"handle_monitor_*.log\" -mtime +${MAX_LOG_DAYS} -delete# 日志函数log() { echo \"$(date \"+%Y-%m-%d %H:%M:%S\") $1\" >> ${LOG_FILE}}# 获取进程FD数量的高效方法get_fd_count() { local pid=$1 if [ -d /proc/$pid/fd ]; then echo $(ls -1 /proc/$pid/fd 2>/dev/null | wc -l) else echo 0 fi}# 主监控循环while true; do log \"===== 开始监控 =====\" total_warning=0 # 高效获取所有PID pids=$(find /proc -maxdepth 1 -type d -name \'[0-9]*\' -printf \"%f\\n\" 2>/dev/null) # 遍历PID for pid in $pids; do fd_count=$(get_fd_count $pid) if [[ $fd_count -ge $THRESHOLD ]]; then proc_name=$(ps -p $pid -o comm= 2>/dev/null || echo \"未知进程\") log \"警告: PID $pid ($proc_name) - $fd_count 个文件描述符\" ((total_warning++)) # 高危进程详细记录 if [[ $fd_count -ge $HIGH_THRESHOLD ]]; then log \"高危进程详情:\" lsof -p $pid +c 0 2>/dev/null | head -50 >> ${LOG_FILE} log \"(仅显示前50个FD,完整列表请单独检查)\" fi fi done # 系统级统计 sys_stats=$(cat /proc/sys/fs/file-nr 2>/dev/null) log \"系统FD统计: ${sys_stats:-无法获取}\" log \"警告进程总数: $total_warning\" log \"===== 监控结束 =====\" sleep $INTERVALdone
使用说明
使用说明:
- 保存为
fd_monitor.sh
- 赋予执行权限:
chmod +x fd_monitor.sh
- 后台运行:
nohup ./fd_monitor.sh &
- 查看日志:
tail -f logs/handle_monitor_xxx.log