记一次奇怪的文件句柄泄露问题

这篇具有很好参考价值的文章主要介绍了记一次奇怪的文件句柄泄露问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

记录并分享一下最近工作中遇到的 Too many open files 异常的解决过程。

问题背景

产品有个上传压缩包并导入配置信息到数据库中的功能,主要流程如下:

  1. 用户上传压缩包;
  2. 后端解压存放在临时目录,并返回列表给用户;
  3. 用户选择需要导入哪些信息;
  4. 后端按需插入数据库中,完成后删除临时目录。

这个功能上线两三年了,一直没出现问题,最近测试在功能回归时,导入的时候出现Too many open files异常。

但是通过lsof -p pid | wc -l查看当前进程打开的句柄数时,又是正常的。

Too many open files是Linux系统中常见的错误,字面意思就是说打开了太多的文件,超过了系统的限制。

这里的文件(file)更准确的意思是文件句柄,或者是文件描述符。可以说,Linux系统里的一切都是文件,包括网络连接、端口等等。

lsof -p pid命令可以查看指定进程当前打开的文件信息。wc -l命令指按行统计。

问题分析

当时的第一反应是该系统的文件句柄数配置的太低了,因为在其他环境都是正常的。

通过ulimit -a命令,可以查看当前系统对各种资源的限制。

[uyong@linuxtest ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31767
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 4096
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) 31767
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

open files 那行就是表示打开的文件数限制,即一个进程最多可以同时打开的文件数。

也可以通过ulimit -n直接查看最大打开文件数量。

当时查出来的配置是4096,查看其他没问题的环境上的配置,数量都是远远大于这个数。而且系统重新启动后,没做任何操作时,通过lsof -p pid | wc -l查看文件占用,只有100多个。在好的环境上执行导入成功后,再次查看,文件占用数不变。在有问题的环境上导入失败后,查看文件占用数也是不变。

虽然当时的压缩包文件很大,但4096按说也够的。有点奇怪,为了不影响测试进度,只能先临时修改系统配置,增大文件数限制,ulimit -n 65535命令执行后,再次导入没问题了。

命令ulimit -n 65536只能临时临时调整文件数量,系统重启后配置就会失效。

如果要永久生效,需要在配置文件/etc/security/limits.conf里增加如下两行:

* soft nofile 65536
* hard nofile 65535

问题到此就结束了吗,NO😒,治标不治本,而且测试还顺手提了个遗留缺陷🐛

问题解决

出现这个问题,不用怀疑,肯定就是打开的文件太多了,又没有及时释放,文件越多,占用的句柄就越多。

撸了一遍整个流程的代码,有两个接口:

一个接口用于上传压缩包,解压后,返回资源列表,伪码如下:

public List<Item> upload(MultipartFile zipFile) {
    try {
        // 解压压缩包到临时目录
        unzip(zipFile, tmpDir);
        // 搜集所有的资源返回给前端
        return collectItems(tmpDir);
    } catch (Exception e) {
        // 只要发生了异常,就把临时目录清理了
        clear(tmpDir);
    }
}

一个接口用于处理用户选择的资源并导入到数据库,伪码如下:

public void importDb(List<Item> selected) {
    try {
        // 读取文件并插入数据库
        readFilesAndImportDb(tmpDir, selected);
    } finally {
        // 处理完成后,不管失败与否,都把临时目录清理了
        clear(tmpDir);
    }
}

不管哪个接口,都在最后清理了资源,也就解释了不管导入成功还是失败,查看文件占用情况都是正常的。

逐个方法排查后,最终确定是unzip方法有问题,这里贴一下解压代码,看看你有没有发现问题所在👀

import java.io.*;
import java.util.*;
import org.apache.commons.io.IOUtils;

/**
* @param in zip输入流
* @param outDir 解压后文件存在目录
*/
public void unzip(InputStream in, String outDir) throw IOException {
    try (ZipInputStream zipIn = new ZipInputStream(new BufferedInputStream(in))) {
        ZipEntry zipEntry;
        while((zipEntry = zipIn.getNextEntry()) != null) {
            File outItemFile = new File(outDir, zipEntry.getName);
            if (zipEntry.isDirectory()) {
                outItemFile.mkdirs();
            } else {
                outItemFile.getParentFile().mkdirs();
                outItemFile.createNewFile();
                IOUtils.copy(zipIn, new FileOutputStream(outItemFile));
            }
        }
    }
}

上述这段代码就会导致,压缩包里的文件越多,所占用的文件句柄就越多。

最终,只要再加一行代码就解决问题了:

try (FileOutputStream fos = new FileOutputStream(outItemFile)) {
    IOUtils.copy(zipIn, fos);
}

根本原因就是apache的IOUtils.copy方法并不会主动关闭输出流。文章来源地址https://www.toymoban.com/news/detail-747928.html

总结

  1. 常见的工具方法,能用现成的就用现成的,轮子可以自己慢慢刨析,私下里学习研究重造;
  2. 要了解所使用的第三方工具方法,它们会不会影响入参的状态,比如这里的copy方法,会不会主动关闭输入输出流。

参考资料

  1. Linux下Too many open files问题排查与解决
  2. ulimit命令详解:如何设置和查看系统资源限制

到了这里,关于记一次奇怪的文件句柄泄露问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 记一次 .NET某账本软件 非托管泄露分析

    中秋国庆长假结束,哈哈,在老家拍了很多的短视频,有兴趣的可以上B站观看:https://space.bilibili.com/409524162 ,今天继续给大家分享各种奇奇怪怪的.NET生产事故,希望能帮助大家在未来的编程之路上少踩坑。 话不多说,这篇看一个 .NET程序集泄露 导致的CLR私有堆泄露的案例,

    2024年02月08日
    浏览(30)
  • 记一次 .NET 某医院预约平台 非托管泄露分析

    前几天有位朋友找到我,说他的程序有内存泄露,让我帮忙排查一下,截图如下: 说实话看到 32bit, 1.5G 这些之后,职业敏感告诉我,他这个可能是虚拟地址紧张所致,不管怎么说,有了 Dump 就可以上马分析。 要看是不是虚拟地址紧张,可以用 !address -summary 观察下内

    2024年02月12日
    浏览(35)
  • 得物-Golang-记一次线上服务的内存泄露排查

    在风和日丽的一天,本人正看着需求、敲着代码,展望美好的未来。突然收到一条内存使用率过高的告警。 告警的这个项目,老代码是python的,最近一直在go化。随着go化率不断上升,发现内存的RSS使用率越飙越高。最终达到容器内存限制后,进程会自动重启。RSS如下图所示

    2024年02月04日
    浏览(46)
  • 记一次 .NET某新能源MES系统 非托管泄露

    前些天有位朋友找到我,说他们的程序有内存泄露,跟着我的错题集也没找出是什么原因,刚好手头上有一个 7G+ 的 dump,让我帮忙看下是怎么回事,既然找到我了那就给他看看吧,不过他的微信头像有点像 二道贩子 ,不管到我这里是不是 三道 ,该分析的还得要分析呀。😄

    2024年02月08日
    浏览(28)
  • RK3588 MPP解码句柄泄露问题记录

    最近在用瑞芯微3588开发板做一个视频处理的项目,前两天拷机发生了闪退,弹出的问题是“打开文件过多”,经过初步排查定位到是MPP硬解码部分出的问题。 我的MPP解码部分主要用来读取网络相机rtsp流,主要参考了一个github项目GitHub - MUZLATAN/ffmpeg_rtsp_mpp: ffmpeg 拉取rtsp h264流

    2024年02月09日
    浏览(79)
  • 记一次地市hw:从供应商到目标站再到百万信息泄露

    起因:某市hw、给了某医院的资产,根据前期进行的信息收集就开始打,奈何目标单位资产太少,唯有一个IP资产稍微多一点点,登录框就两个,屡次尝试弱口令、未授权等均失败。 尝试互联网获取更多目标资产的信息。fofa搜索IP发现这样一个系统,是通用型的系统(根据指

    2024年02月08日
    浏览(27)
  • 记一次配置文件spring.profiles.active环境变量不生效的问题

    三个环境的配置文件 分别是grayTg, proTg, testTg application.properties设置环境变量由IDEA统一管理  Maven中配置profile能让你为不同的环境定义不同的构建;包括本地测试环境,灰度环境, 生产环境这三个环境。可以用如下的标签配置在pom.xml文件中 本地Docker打包时, 无法解析环境配置

    2024年01月17日
    浏览(34)
  • 记一次死锁问题

    最近在做一个需求,碰到了死锁的问题,记录下解决问题的过程 这个需求要改动一个接口,我这边称为A接口,原先的逻辑是A接口内部会调用c方法,c方法是一个dubbo方法, 现在需要再A接口里添加调用B方法,b方法是本地调用。 A接口的入参是某个商品的编码,拿到这个商品编

    2023年04月26日
    浏览(37)
  • 记一次重大的问题解决

    我们是需要的操作两个git仓库的的三个分支(此处第一个仓库简称:A(负责程序的第一层进入),第二个简称B(负责业务的执行)) 大致就是A的代码引用了B,B的代码引用了A,互为对方的jar包。 问题(部署到测试域之后): 1:请求打进来之后,有时候进,有时候不进,有

    2024年02月21日
    浏览(36)
  • 如何洞察 .NET程序 非托管句柄泄露

    很多朋友可能会有疑问,C# 是一门托管语言,怎么可能会有非托管句柄泄露呢? 其实一旦 C# 程序与 C++ 语言交互之后,往往就会被后者拖入非托管泥潭,让我们这些调试者被迫探究 非托管领域问题 。 为了方便讲述,我们上一个 Event 泄露的案例,使用 C# 调用 C++ ,然后让

    2024年02月12日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包