删除文件为啥磁盘依然爆满

共 3281字,需浏览 7分钟

 ·

2021-03-20 10:32

01


问题发现


最近在测试遇到一个问题,容器日志过大导致系统磁盘爆满,造成的影响就是该服务器的一些服务挂掉了。日志过大,解决方法就是删啊,直接定位到容器日志位置发现高达5G,三下五除二就rm了,但是仍然显示磁盘空间已满,但是du -sh命令也显示文件夹下为空。经过同事百度得知,容器日志不能直接rm,要通过cat /dev/null > {log文件}方式将日志删除。因为该文件被进程所引用,直接删除并不能擦除磁盘上的文件block信息,解决方式就是停止进程。今天就来复现一下并探究一下底层原理。

02


设计复现


由于容器也属于一种进程,引用文件的方式并没有不同于其他普通进程,所以就使用一个简单的demo复现。

首先进入一个目录,以/tmp/testfile目录为例,可以看到我的服务器还有2G的剩余空间

[centos@guozhao testfile]$ cd /tmp/testfile
[centos@guozhao testfile]$ df -h .
文件系统       容量 已用 可用 已用% 挂载点
/dev/vda1       50G   49G  2.0G   97% /

然后生成一个随机文件,再来查看剩余空间,看到还剩余1014M空间

[centos@guozhao testfile]$ dd if=/dev/urandom of=/tmp/testfile/delfiletest   bs=1M count=1000
记录了1000+0 的读入
记录了1000+0 的写出
1048576000字节(1.0 GB)已复制,8.31491 秒,126 MB/秒
[centos@guozhao testfile]$
[centos@guozhao testfile]$ df -h .
文件系统       容量 已用 可用 已用% 挂载点
/dev/vda1       50G   49G 1014M   99% /

启动一个程序,引用该文件,不退出进程

func main() {
    file, err := os.Open("/tmp/testfile/delfiletest")
    defer file.Close()
    if err != nil{
    fmt.Println("open err :",err.Error())
    return
    }
    time.Sleep(100*time.Minute)
}

接下来删除该文件文件,查看磁盘占用情况,看到虽然文件删除,但是磁盘空间并没有释放。

[centos@guozhao testfile]$ rm -rf delfiletest
[
centos@guozhao testfile]$ df -h .
文件系统       容量 已用 可用 已用% 挂载点
/dev/vda1       50G   49G 1015M   99% /

解决方法,找到引用该文件的进程,并停止该进程,可以Ctrl+C停止,也可以kill命令停止。

[centos@guozhao testfile]$ lsof | grep deleted|grep delfile
l
sof: WARNING: can't stat() proc file system /run/docker/netns/default
    Output information may be incomplete.
......
testdelet 29604 29608 centos   3r     REG  253,1 1048576000 58925156 /tmp/testfile/delfiletest (deleted)
......
[centos@guozhao testfile]$ kill 29604

停止后再次查看磁盘空间,发现磁盘已经释放。

[centos@guozhao testfile]$ df -h .
文件系统       容量 已用 可用 已用% 挂载点
/dev/vda1       50G   49G  2.0G   97% /

重做一次上面的步骤,这次我们使用正确的方式释放磁盘的空间,可以看到磁盘空间腾出来了。

[centos@guozhao testfile]$ cat /dev/null > /tmp/testfile/delfiletest 
[centos@guozhao testfile]$
[centos@guozhao testfile]$ df -h .
文件系统       容量 已用 可用 已用% 挂载点
/dev/vda1       50G   49G  2.0G   97% /

03


原理考究


继续深挖一下造成这种结果的原因。

在Linux上,每个文件都有一个自己对应的索引节点即inode,在这个inode里记录了文件在磁盘的块信息,以及链接数量等信息,一个文件在是否要被真正删除释放空间,取决于两个值,一个是 i_count ,代表引用计数;一个是 i_nlink ,代表硬链接数量,只有当两个都为0,文件才会真正释放。

struct inode{
   atomic_t i_count;
   unsignet int i_nlink;
  ......
};

当有进程使用该文件时候, i_count 就会加1,当进程不在引用或进程结束,就会减一。

硬链接也是如此,当为文件创建一个硬链接时, i_nlink 就会加一,删除就会减一,当减少为0时候,就会删除文件,释放空间。

在Linux中,硬链接指的是文件名与inode的链接,通常创建一个文件对应一个硬链接,我们可以手动通过ln命令或者程序触发link系统调用为一个文件创建一个硬链接,相当于两个文件名对应了同一个磁盘文件,两个都删除才会删除磁盘文件(没有进程引用的前提下)。而Linux的rm命令相当于执行了unlink系统调用,会使得i_nlink数量减1。

在上述示例中,由于文件并没有被真正删除,所以该文件是可以恢复的,只需要找到进程的pid,并进入 /proc/{pid}/fd 中,找到对应的文件描述符,执行 cp 命令复制即可找回文件。



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报