linux 环境服务发生文件句柄泄漏导致服务不可用

问题描述:

服务调用远程rest接口 报错,发生too many open files 异常,系统句柄资源耗尽,导致服务不可用。

排查经过:

1、针对报错代码进行本地构建,构造异常,并进行压测。问题未复现

2、经过讨论分析,问题发生根因为:句柄资源耗尽,那么必然存在进程持续消耗句柄资源,且不进行释放。

3、因此,开始排查服务进程句柄信息

bash 复制代码
# 统计进程句柄总数
lsof -p <PID> | wc -l 
# 统计进程目录句柄总数  若持续增加则存在泄漏
lsof -p <PID> | grep DIR  
# 统计进程delete文件删除句柄总数  若持续增加则存在泄漏
lsof -p <PID> | grep delete

通过统计观察各个进程的句柄总数,分析查找到问题根源

原因最终确认:

问题代码:loadDirectory函数加载指定目录下所有文件,并返回Path列表。Files.list函数为安全关闭文件句柄,导致该方法频繁调用时未释放目录句柄资源。导致系统句柄资源耗尽

java 复制代码
    public static List<Path> 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 方式主动关闭资源

java 复制代码
public static List<Path> loadDirectory(String fileDirectory) throws CheckingException {
        try (Stream<Path> 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,则提示高危进程

bash 复制代码
#!/bin/bash
# 高效进程句柄监控脚本 v2.0
THRESHOLD=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 $INTERVAL
done

使用说明

使用说明:
  1. 保存为 fd_monitor.sh
  2. 赋予执行权限:chmod +x fd_monitor.sh
  3. 后台运行:nohup ./fd_monitor.sh &
  4. 查看日志:tail -f logs/handle_monitor_xxx.log
相关推荐
数智工坊5 小时前
Zotero自建Ubuntu WebDAV附件同步方案(完美解决存储空间不足)
linux·运维·ubuntu
ltl7 小时前
HTTP/3 实战:从 QUIC 到 H3 的完整请求链路
linux
ltl7 小时前
POSIX 文件锁:flock、fcntl 与 NFS 上的工程陷阱
linux
ltl7 小时前
CUDA 生态:cuBLAS、cuDNN、NCCL、Triton、CUTLASS
linux
饺子大魔王的男人7 小时前
CentOS部署Cockpit:图形化查看系统、日志与服务状态
linux·运维·centos
不会就选b8 小时前
Linux之信号(二)
linux·运维·服务器
我是小灰灰吖8 小时前
QT在线安装器使用指南:从下载到配置完整教程
linux·qt·ubuntu
GlueNa2SiO39 小时前
第十八章 Linux故障排查与恢复
linux·服务器·笔记·学习
寺中人9 小时前
Linux 基础命令入门实战教程:从零掌握常用操作,新手快速上手
linux·运维·服务器·shell·linux 命令·linux 基础教程·linux 入门
小张成长计划..10 小时前
【Linux】18:基础IO
linux·运维·服务器