由一次磁盘告警引发的“血案”——你知道 du 和 ls 区别吗?
来源 | 程序猿石头
责编 | Carol
封图 | CSDN下载自视觉中国
图来源于 SkyPixel
知道为什么会有上面的结果吗?什么又是稀疏文件?这篇文章将为你揭秘。
问题背景
确切地说,不是收到的自动告警短信或者邮件告诉我某机器上的磁盘满了,而是某同学人肉发现该机器写不了新文件才发现该问题的. 说明我司告警服务还不太稳定 :)
第一次出现该问题时,我的处理方式是:先删了 /tmp/
目录, 空闲出部分空间,然后检查下几个常用的用户目录,最终发现某服务A的日志文件(contentutil.log)占用了好几个大G,询问相关开发人员后确定该日志文件不需要压缩备份,所以可直接删除, 于是 rm contentutil.log
之后就天真地认为万事大吉了…(不懂为啥当初没 df
再看看)
然而大约xx天后,发现该机器磁盘又满了,惊呼奇怪咋这么快又满了。最终发现是上次 rm
后,占用好几个大G的 contentutil.log 一直被服务A的进程打开了,空间并没有释放。rm 其实是删除该文件名到文件真正保存到磁盘位置的链接,此时该文件句柄还被服务A打开,因此对应的数据并没有被回收,其实可以理解为 GC 里面的引用计数,rm
只是减少了引用计数,并没有真正的进行释放内存,当引用计数为0的时候,OS 内核才会释放空间,供其他进程使用。所以当A进程停止(文件句柄的引用计数会变为0)或者重启后,占用的存储空间才被释放(从某种程度上讲说明该服务一直很稳定,可以连续跑很久不出故障~ 微笑脸)。(tip:如果不知道具体进程或文件名的话:lsof | grep deleted
,这样会查找所有被删除的但是文件句柄没有释放的文件和相应的进程,然后再kill掉进程或者重启进程即可)。
其实可以简单用修改文件内容的方式(例如echo "">contentutil.log
)在不用重启进程的情况下释放空间。
du vs ls
前两天该问题又出现了,该服务A的日志文件(contentutil.log)占用了约7.6G(请原谅我们没有对该服务的日志做logrotate)。
这一次学聪明了,直接用echo 'hello' > contentutil.log
,然后 df 确认磁盘空间确实已经释放,心想着这次可以 Happy 了,突然手贱执行了下 ls 和 du,有了以下结果:
[root@xxx shangtongdai-content-util]# ls -lah contentutil.log
-rw-r--r--. 1 root root 7.6G Nov 7 19:36 contentutil.log
[root@xxx shangtongdai-content-util]# du -h contentutil.log
2.3M contentutil.log
反正我看到这样的结果是百思不得其解。可以明确的是,这里的 ls 和 du 结果肯定代表不同的含义,具体原因不详,在查阅相关资料和咨询强大的票圈后了解到,这大概与文件空洞和稀疏文件(holes in ‘sparse’ files)相关。
ls 的结果是 apparent sizes,我的理解是文件长度,就类似文件系统中 file 这个数据结构中的定义文件长度的这个字段,du 的结果 disk usage,即真正占用存储空间的大小,且默认度量单位是 block。(apparent sizes 和 disk usage 说法摘自 man du 中的 --apparent-size 部分)
给出一个具体的示例:
// Mac OS 10.11.6 (15G1004)
➜ _drafts git:(source) ✗ echo -n a >1B.log
➜ _drafts git:(source) ✗ ls -las 1B.log
8 -rw-r--r-- 1 tanglei staff 1 11 9 00:06 1B.log
➜ _drafts git:(source) ✗ du 1B.log
8 1B.log
➜ _drafts git:(source) ✗ du -h 1B.log
4.0K 1B.log
上面示例中,文件 1B.log 内容仅仅包含一个字母”a”,文件长度为1个字节,前面的 8 为占用的存储空间 8 个 block,(ls -s 的结果跟 du 的结果等价,都是实际占用磁盘的空间),为什么1个字节的文件需要占用8个 block 呢?可以这样理解, block 为磁盘存储的基本的单位,方便磁盘寻址等(这里说的基本单位应该是磁盘物理结构单位例如一个扇区/柱面等, 对应一个物理单位),而此处的block可以理解为一个逻辑单位,且一个文件除了包括数据外,还需要存储描述此文件的其他信息,因此包含1个字节的文件实际在磁盘中占用的存储空间不止1个字节。默认情况下,Mac中1个逻辑block中是 512 字节,因此 du -h 结果是 8 * 512 = 4096 = 4.0K。
If the environment variable BLOCKSIZE is set, and the -k option is not specified, the block counts will be displayed in units of that size block. If BLOCKSIZE is not set, and the -k option is not specified, the block counts will be displayed in 512-byte blocks. (
man du
)
因此,通常情况下 ls
的结果应该比 du
的结果更小(都指用默认的参数执行,调整参数可使其表达含义相同),然而上面跑服务 A 的机器上 contentutil.log 的对比结果是 7.6G vs. 2.3M
, 仍然无法理解了。
沿着 man du 可以看到:
although the apparent size is usually smaller, it may be larger due to holes in (‘sparse’) files, internal fragmentation, indirect blocks, and the like
即因contentutil.log是一个稀疏文件,虽然其文件长度很大,到7.6G了,然而其中包含大量的holes
并不占用实际的存储空间。
talk is cheap
下面用一个具体的例子来复现以上遇到的问题。注意以下例子为 Linux version 2.6.32 (Red Hat 4.4.7)中运行结果,且在 Mac 中并不能复现(后文有指出为什么我的Mac不能复现)。
// 从标准输入中读取 count=0 个block, 输出到 sparse-file 中,
// 一个 block 的大小为1k(bs=1k), 输出时先将写指针移动到 seek 位置的地方
[root@localhost ~]# dd of=sparse-file bs=1k seek=5120 count=0
0+0 records in
0+0 records out
0 bytes (0 B) copied, 1.6329e-05 s, 0.0 kB/s
// 所以此时的文件长度为: 5M = 5120*1k(1024) = 5242880
[root@localhost ~]# ls -l sparse-file
-rw-r--r--. 1 root root 5242880 Nov 8 11:32 sparse-file
[root@localhost ~]# ls -ls sparse-file
0 -rw-r--r--. 1 root root 5242880 Nov 8 11:32 sparse-file
// 而 sparse-file 占用的存储空间为 0 个 block
[root@localhost ~]# du sparse-file
0 sparse-file
[root@localhost ~]# du -h sparse-file
0 sparse-file
此时若用 vim 打开该文件,用二进制形式查看 (tip :%!xxd
可以更改当前文件显示为2进制形式),能看到里面的内容全是0。
或者直接用od
命令查看2进制。
// vim 二进制查看
0000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
....
//od -b sparse-file
0000000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
24000000
实际上,Sparse 文件是并不占用磁盘存储空间的,那为什么能看到文件里面包含很多0? 因为当在读取稀疏文件的时候,文件系统根据文件的 metadata(就是前面所指描述文件的这个数据结构)自动用0
填充[ref Wiki]; Wiki上还说,现代的不少文件系统都支持 Sparse 文件,包括 Unix 及其变种和 NTFS,然而Apple File System(APFS)不支持,因此我在我的 Mac 上用 du
查看占用空间与 ls
的结果一致。传闻指出 Apple 在今年6月的 WWWC 上宣称支持 Sparse 文件。(貌似目前我的系统版本还不支持)
// In Mac
➜ ~ dd of=sparse-file bs=1k seek=5120 count=0
0+0 records in
0+0 records out
0 bytes transferred in 0.000024 secs (0 bytes/sec)
➜ ~ ls -ls sparse-file
10240 -rw-r--r-- 1 tanglei staff 5242880 11 9 09:44 sparse-file
➜ ~ du sparse-file
10240 sparse-file
以上是用 dd
等命令创建稀疏文件,也有同学用 c 代码实现了相同的功能。其实就是写文件的时候,改变下当前文件写指针。前面遇到的问题就应该类似。
#include <stdio.h>
#include <fcntl.h>
#include <string.h>int main() {int fd, result;char wbuf[] = "hello";if ((fd = open("./filetest.log", O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR))) {perror("open");return -1;}if ((result = write(fd, wbuf, strlen(wbuf)+1)) < 0) {perror("write");return -1;}if ((result = lseek(fd, 1024*1024*10, SEEK_END)) < 0) {perror("lseek");return -1;}if ((result = write(fd, wbuf, strlen(wbuf)+1)) < 0) {perror("write");return -1;}close(fd);return 0;
}
以上先将”hello”写入filetest.log,然后改变文件指针到1024*1024*10
(相当于文件长度这个字段变大了),gcc 编译后运行结果文件详情如下:
[root@localhost ~]# ls -ls filetest.log
8 -rw-------. 1 root root 10485772 Nov 9 17:45 filetest.log
[root@localhost ~]# du filetest.log
8 filetest.log
[root@localhost ~]# du -h filetest.log
8.0K filetest.log
[root@localhost ~]# ls -lh filetest.log
-rw-------. 1 root root 11M Nov 9 17:45 filetest.log
[root@localhost ~]# od -c filetest.log
0000000 h e l l o \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
50000000 \0 \0 \0 \0 \0 \0 h e l l o \0
50000014
解释下结果: 文件长度应该是 “hello” 加上 “\n” 共6个字节*2 = 12
,再加上1024*1024*10
个字节,即为ls
产生的结果10485772个字节约11M,而du
的结果为8个block也为8k(这台机器上的block大小与前面的Mac不一样,这里是1024)。
Display values are in units of the first available SIZE from –block-size, and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set. (
du --help
)
总结
总结一下:出现以上问题说明自己对一些基础掌握得尚不牢固,比如:
rm 某文件后, 文件占用的磁盘空间并不是立即释放, 而是其句柄没有被任意一个进程引用时才回收;
ls/du 命令结果的具体含义;
稀疏文件。
然而这些知识点都在《UNIX环境高级编程》这本书中有讲 (之前走马观花看过不少,咋对稀疏文件等一点印象都木有!)
以上内容若有不清楚或不正确的地方,还望大家指出,感谢。
6月3日20:00,CSDN 创始人&董事长、极客帮创投创始合伙人蒋涛携手全球顶级开源基金会主席、董事,聚焦中国开源现状,直面开发者在开源技术、商业上的难题,你绝不可错过的开源巅峰对谈!立即免费围观:
推荐阅读
因为一个跨域请求,我差点丢了饭碗
没错,你离分布式搜索只差一个Elasticsearch入门!
Python开发之:Django基于Docker实现Mysql数据库读写分离、集群、主从同步详解 | 原力计划
全球Python调查报告:Python 2正在消亡,PyCharm比VS Code更受欢迎
无代码来了,还要程序员吗?
再见,Eclipse | 原力计划
区块链共识算法总结 | 原力计划
真香,朕在看了!
由一次磁盘告警引发的“血案”——你知道 du 和 ls 区别吗?相关推荐
- 由java:local_policy.jar和US_export_policy.jar引发的“血案”
起因:今天项目上线,上线后监测日志,发现由异常,开始查找问题 ,进而引发了"血案" 线上日志报错如下:Illegal key size 画外音:看到线上项目出现问题心里慌的一批 赶 ...
- 波涛汹涌的黄金甲,一碗中药引发的血案!
严重声明:网路转载 主要情节: 父王(周润发)说母后(巩利)身体虚寒,需要每天定时服用亲自配置的中药,已服用了几十年.而父王早就知道了母后和太子元祥(刘烨)之间的苟且之事,远征回宫后在其中药中加入一味 ...
- mysql backlog_一次优化引发的血案
前些天一个Nginx+PHP项目上线后遭遇了性能问题,于是打算练练手,因为代码并不是我亲自写的,所以决定从系统层面入手看看能否做一些粗线条的优化. 首先,我发现服务的Backlog设置过小,可以通过s ...
- 第三方账号登陆的过程及由此引发的血案
72agency · 2014/03/19 10:40 0x00 前言 第三方账号登陆也就是当你没有A网站的注册账号时,你可以使用该与A网站合作的第三方账号登陆A,在大多数情况下你会立即拥有与你第三方 ...
- 一个普通ERROR 1135 (HY000)错误引发的血案:
一个普通ERROR 1135 (HY000)错误引发的血案: 今天接到测试人员反应,测试环境前端应用程序无连接mysql数据库,登录mysql服务器,查看错误日志,发现有如下报错: 点击(此处)折叠或 ...
- 一次 Druid 连接池泄露引发的血案!
最近某个应用程序老是卡,需要重启才能解决问题,导致被各种投诉,排查问题是 Druid 连接池泄露引发的血案.. 异常日志如下: ERROR - com.alibaba.druid.pool.GetCo ...
- 线上 CPU100% 异常案例:一个正则表达式引发的血案
前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%.通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息. 我们可以看到所 ...
- 一场由过滤器Filter引发的血案
一场由过滤器Filter引发的血案 事件起因 本来应该是下图的登录界面 变成了这样 What's the fuck????? 抓狂 原因 解决方法: 在过滤器中给资源文件开个绿色通道
- 一个由正则表达式引发的血案
阿里妹导读:周末快到了,今天为大家送上一篇很有意思的小文章,具有提神醒脑之功效.作者是来自阿里巴巴LAZADA产品技术部的申徒童鞋. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中 ...
最新文章
- 让Mac OS 10.x.x安装在Vmware虚拟机上!
- 1000万贷款三年,到期一次性偿还1500万,这个利息算不算高?
- 提前还清贷款为什么还要付违约金?
- 【数字逻辑入门】计算机如何存储1位二进制数
- linux django搭建网站,Linux下搭建Django站点一
- Process com.xxxxxxxx has died
- CentOS7 安装Redis Cluster集群
- 20150823 命令练习总结
- 高质量外链该怎样做?
- kvm初体验之八:调整vm的vcpu, memory, disk大小
- github加速方法
- Android Activity 硬件加速
- 2013-06-16 读书笔记 大前研一 《无国界的世界》
- C# Thread详解
- 机器学习中的过拟合与欠拟合
- 揭秘互联网行业“新”三大巨头
- 宣州谢朓楼饯别校书叔云
- 酷睿i7 9750h相当于什么水平 i79750h属于哪个档次
- 读心神探感悟 读心神探 语录 读心神探 观后感
- 全新 Amazon RDS for MySQL 和 PostgreSQL 多可用区 (Multi-AZ) 部署选项