某天,收到监控系统的告警信息,说磁盘空间占用过高,登上服务器,使用 df -h
一看,发现磁盘占用率已经 96%了:
通过查看 /usr/local/nginx/conf/vhost/xxx.conf
找到 access_log 和 error_log 的路径,进去后,du -sh
查看了下总大小,一共占用了 31GB,二话不说,直接 rm xxx_access.log
删除掉nginx的 access_log 文件,然后兴冲冲的 df -h
一看,磁盘空间并没有下降,还是占用96%,什么鬼?
为什么文件被进程占用之后删除不会释放磁盘空间呢?
因为在 Linux 中,文件存储在硬盘上的最小存储单位是扇区(Sector),每个 sector 只有 512字节大小;多个 sector 可以组成文件块 (block) 。当我们读取某个文件数据的时候,操作系统就需要知道这个文件存储在哪个 block 上。文件的数据存放位置信息被存放到了 inode (索引节点)上。也就是说,在 Linux 下,文件由指针部分(inode)和数据部分(data)组成。
因此,执行 rm xxx
命令删除文件的时候,只是删掉了inode数据,而文件的实际数据部分在 inode 被清除掉之后,会被覆盖并写入新的内容。但是如果文件在删除的时候是被打开的(有一个进程正在使用该文件,文件被进程锁定或者有进程一直在向这个文件写数据等)状态,那么进程依旧可以读取该文件,系统就会认为该文件的磁盘空间一直被占用。
虽然删除了 access_log 文件,但是由于 nginx 进程还在一直向这个文件写入内容,文件的 inode 并没有清除掉,系统内核认为文件并未删除,这才出现空间不释放的情况。也就是说:当一个进程持续的写入一个文件的时候,直接删除这个文件,磁盘空间并不会得到释放。
可以通过 lsof | grep deleted | grep access_log
命令查看是不是有进程一直还在写入这个文件:
可以看到这个文件被进程 nginx 锁定,而且 nginx 进程一直在往这个文件写数据,最后一列的 deleted 状态表示这个文件已经被删除。由于进程还在一直往里面写数据,导致磁盘空间并未释放。
解决方案:
- 方法1:写入一个空数据到这个文件中:
cat /dev/null > /home/wwwlogs/access.log
或者echo " " > /home/wwwlogs/access.log
; - 方法2:重启 nginx 进程让操作系统回收磁盘空间:
/usr/local/nginx/sbin/nginx -s reload
;
这样操作后,磁盘空间直接下降了:
另外,有一些查看文件相关的命令,记录下来备用:
# 查看当前磁盘占用
df -h
# 查看当前目录大小
du -sh
# 查看当前目录下第一层文件夹的大小,按照由大到小排序
du -sh * | sort -rh (文件过多的话,可以加 | less)
# 同上,但可以指定层级
du -lh --max-depth=1 | sort -rh
# 查看进程打开的文件
lsof | grep xxx (记得加上grep,否则返回的数据会很多)
后续工作:
由于此前没有对nginx的日志进行切割,导致nginx的日志文件越来越大,因此,需要写一个shell脚本,通过定时任务按照每天(或者其他自定义维度)对日志文件切割存储,并自动删除一段时间之前的日志文件。
shell脚本文件:vim /root/cut_nginx_log.sh
#!/bin/bash
#日期参数
date=`date -d "yesterday" +"%Y%m%d"`
# 复制原来的日志文件
cp /home/wwwlogs/access.log /home/wwwlogs/backup/access_${date}.log
# 清空原有的日志文件
cat /dev/null > /home/wwwlogs/access.log
# 删除7天前的日志文件
find /home/wwwlogs/backup -mtime 7 -type f -name \*.log | xargs rm -f
增加执行权限: chmod +x /root/cut_nginx_log.sh
定时任务:crontab -e
0 0 * * * /root/cut_nginx_log.sh