Linux磁盘占满,du df不一致,Java文件流未关闭导致的句柄泄漏,lsof | grep deleted | sort -nr

关键词:Linux、文件句柄泄露、磁盘空间占满、du、df、lsof、Java、资源释放

问题

最近同一天,发现两起由于磁盘空间占满引发的问题

  1. 某1服务器 rocketmq 刷盘失败

org.apache.rocketmq.client.exception.MQBrokerException: CODE: 14 DESC: service not available now, maybe disk full, CL: 0.90 CQ: 0.90 INDEX: 0.90, maybe your broker machine memory too small.

  1. 某2服务器 redis rdb失败

io.lettuce.core.RedisCommandExecutionException: MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

查看磁盘空间

命令

# du:-Disk Usage
du -f

# df:-Disk Free
df -h

du命令会对待统计文件逐个调用fstat,获取文件大小。数据是基于文件获取的,可以跨越多个分区操作。如果针对的目录中文件很多,du速度就会很慢。

df命令使用statfs,直接读取分区的超级块信息获取分区使用情况。数据是基于分区元数据的,所以只能针对整个分区。由于df直接读取超级块,所以运行速度不受文件多少影响。

现象

下面数据是修改过的,还原之前的磁盘100%情况

在根目录查看已有文件的磁盘空间占用情况

[root@172-23-27-27 /]# du -h --max-depth=1|sort -nr
697M    ./run
600M    ./opt
209M    ./var
127M    ./boot
46M     ./tmp
30M     ./etc
12G     .
7.9M    ./dev
7.7G    ./home
1.3G    ./root
1.1G    ./usr
0       ./sys
0       ./srv
0       ./proc
0       ./mnt
0       ./media

使用总共12G

查看系统可用磁盘空间

[root@localhost /]# df -h
文件系统             容量  已用  可用 已用% 挂载点
devtmpfs             7.8G  4.0K  7.8G    1% /dev
tmpfs                7.8G     0  7.8G    0% /dev/shm
tmpfs                7.8G  803M  7.0G   11% /run
tmpfs                7.8G     0  7.8G    0% /sys/fs/cgroup
/dev/mapper/cl-root   50G   48G  2.0G   98% /
/dev/sda1           1014M  232M  783M   23% /boot
/dev/mapper/cl-home   42G   25G   17G   60% /home
tmpfs                1.6G     0  1.6G    0% /run/user/0
tmpfs                1.6G     0  1.6G    0% /run/user/1000

剩余空间2G

很明显,有36G磁盘空间被谁吃了,离谱,我在这12G里删除无用文件也没什么用

查看已删除还占用磁盘的进程

命令

# losf: lists openfiles
lsof | grep deleted | sort -nr

lsof命令用于查看你进程开打的文件,打开文件的进程,进程打开的端口(TCP、UDP)

deleted的就是已经删除,但是被进程占用,没有释放磁盘空间的文件

补充

ulimit -a 查看服务器打开文件限制
下面命令可以看某进程占用文件数量
ps -ef|grep 进程名称
lsof -p 进程pid| wc -l

现象

27服务器上主要是这类,24910是nameserver的,25119是broker的,20692是console的

java      24910 24914    root   37w      REG              253,0   88204584   67350266 /root/logs/rocketmqlogs/namesrv.log (deleted)
java      24910 24914    root   34w      REG              253,0       5424   67614931 /root/logs/rocketmqlogs/namesrv_default.log (deleted)
java      24910 24913    root   37w      REG              253,0   88204584   67350266 /root/logs/rocketmqlogs/namesrv.log (deleted)
java      24910 24913    root   34w      REG              253,0       5424   67614931 /root/logs/rocketmqlogs/namesrv_default.log (deleted)
java      24910 24912    root   37w      REG              253,0   88204584   67350266 /root/logs/rocketmqlogs/namesrv.log (deleted)
java      24910 24912    root   34w      REG              253,0       5424   67614931 /root/logs/rocketmqlogs/namesrv_default.log (deleted)
java      24910 24911    root   37w      REG              253,0   88204584   67350266 /root/logs/rocketmqlogs/namesrv.log (deleted)
java      24910 24911    root   34w      REG              253,0       5424   67614931 /root/logs/rocketmqlogs/namesrv_default.log (deleted)

把和功能无关的console kill掉重启,磁盘占用直接降到11%,释放了36个G

36服务器也有rocketmq的进程,占用了很多空间,也有下面这个程序的问题

java      12915 12917    root  159r      REG              253,0      154470   34937645 /opt/*********/temp.file (deleted)
java      12915 12917    root  152r      REG              253,0      524545     758403 /opt/*********/temp.file (deleted)
java      12915 12917    root  132r      REG              253,0      524716   34567592 /opt/*********/temp.file (deleted)
java      12915 12917    root  119r      REG              253,0      542470     758407 /opt/*********/temp.file (deleted)
java      12915 12917    root  118r      REG              253,0      547116  101266641 /opt/*********/temp.file (deleted)

rocketmq的问题我们管不着,可以通过重启服务解决,或升级版本解决尝试,我们自己的程序,还是要解决一下问题的

句柄/文件描述符 泄漏

句柄/文件描述符

参考 Linux的句柄是什么?

什么是句柄

  1. 句柄就是一个标识符,只要获得对象的句柄,我们就可以对对象进行任意的操作。
  2. 句柄不是指针,操作系统用句柄可以找到一块内存,这个句柄可能是标识符,map的key,也可能是指针,看操作系统怎么处理的了。fd算是在某种程度上替代句柄吧;Linux 有相应机制,但没有统一的句柄类型,各种类型的系统资源由各自的类型来标识,由各自的接口操作。
  3. 在操作系统层面上,文件操作也有类似于FILE的一个概念,在Linux里,这叫做文件描述符(File Descriptor),而在Windows里,叫做句柄(Handle)(以下在没有歧义的时候统称为句柄)。用户通过某个函数打开文件以获得句柄,此后用户操纵文件皆通过该句柄进行。

粗暴的解释

windowns中是handle,liunx类似的是fd,最早的windows开发书籍,handle是被翻译成“把手”的。虽然不好听,但是个人认为相当传神。

  • 虽然你握住的只是把手,却能拉动整扇门,而且你根本不用在意那门长什么样子
  • 一扇门如果有多个把手,被不同的人(进程)握住,门往哪儿走就不好说了

设计这么一个句柄的原因在于句柄可以防止用户随意读写操作系统内核的文件对象。无论是Linux还是Windows,文件句柄总是和内核的文件对象相关联的,但如何关联细节用户并不可见。内核可以通过句柄来计算出内核里文件对象的地址,但此能力并不对用户开放。

在liunx中的句柄

在linux系统设计里面遵循一切都是文件的原则,即磁盘文件、目录、网络套接字、磁盘、管道等,所有这些都是文件,在我们进行打开的时候会返回一个fd,即是文件句柄。如果频繁的打开文件,或者打开网络套接字而忘记释放就会有句柄泄露的现象。在linux系统中对进程可以调用的文件句柄数进行了限制,在默认情况下每个进程可以调用的最大句柄数是1024个,如果超过了这个限制,进程将无法获取新的句柄,而从导致不能打开新的文件或者网络套接字,对于线上服务器即会出现服务被拒绝的情况。

下面举一个实际的例子,在Linux中,值为0、1、2的fd分别代表标准输入、标准输出和标准错误输出。在程序中打开文件得到的fd从3开始增长。 fd具体是什么呢?在内核中,每一个进程都有一个私有的“打开文件表”,这个表是一个指针数组,每一个元素都指向一个内核的打开文件对象。而fd,就是这个表的下标。当用户打开一个文件时,内核会在内部生成一个打开文件对象,并在这个表里找到一个空项,让这一项指向生成的打开文件对象,并返回这一项的下标作为fd。由于这个表处于内核,并且用户无法访问到,因此用户即使拥有fd,也无法得到打开文件对象的地址,只能够通过系统提供的函数来操作。

在C语言里,操纵文件的渠道则是FILE结构,不难想象,C语言中的FILE结构必定和fd有一对一的关系,每个FILE结构都会记录自己唯一对应的fd。

在程序设计中,句柄是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统 )所管理的内存 块或对象 时,就要使用句柄。

句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象 的控制。

在上世纪80年代的操作系统(如Mac OS 和Windows )的内存管理 中,句柄被广泛应用。Unix 系统的文件描述符 基本上也属于句柄。和其它桌面环境 一样,Windows API 大量使用句柄来标识系统中的对象 ,并建立操作系统与用户空间 之间的通信渠道。例如,桌面上的一个窗体由一个HWND 类型的句柄来标识。

如今,内存容量的增大和虚拟内存算法使得更简单的指针愈加受到青睐,而指向另一指针的那类句柄受到冷淡。尽管如此,许多操作系统仍然把指向私有对象的指针以及进程传递给客户端的内部数组下标称为句柄。

Linux 下的文件描述符(fd)

问题分析

通过pid可以找到对应的程序,根据文件/opt/*********/temp.file (deleted)的关键字,定位问题代码,如下

public void method(InputStream orgIs) throws IOException {
    //以下星号为脱敏数据
    String tempPath = "**********";
    File file = new File(tempPath + File.separator + "temp.file");
    if (!file.getParentFile().exists()) {
        file.getParentFile().mkdirs();
    }
    IOUtils.copy(orgIs, new FileOutputStream(file));
    handle(file);
}

可以看出,IOUtils.copy(orgIs, new FileOutputStream(file));这行代码,只new了文件输出流,但是没有关闭

	IOUtils.copy(orgIs, new FileOutputStream(file));

解决方式

这些是基础操作了

try-catch-finally

	FileOutputStream fileOs = null;
	try {    
        fileOs = new FileOutputStream(file);    
        IOUtils.copy(orgIs, fileOs);
    }
	finally {    
        if (fileOs != null) {        
            fileOs.close();    
        }
    }

try-with-resource

	try (FileOutputStream fileOs= new FileOutputStream(file)){    
        IOUtils.copy(orgIs, fileOs);
    }

lombok的@Cleanup

https://www.jianshu.com/p/f0b1b602d0c9

	@Cleanup FileOutputStream fileOs = new FileOutputStream(file);
	IOUtils.copy(orgIs, fileOs);

总结

第三方程序导致的句柄泄漏,可以通过重启服务解决,或升级版本解决尝试

自己程序的问题,要找到引起问题的点,如文件流未关闭或关闭失败,通过try-catch-finally、try-with-resource或lombok的@Cleanup等方式关闭

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值