一个在阿里云打工的清华学渣!
知道为什么会有上面的结果吗? 什么又是稀疏文件? 这篇文章将为你揭秘.
问题背景
确切地说, 不是收到的自动告警短信或者邮件告诉我某机器上的磁盘满了, 而是某同学人肉发现该机器写不了新文件才发现该问题的. 说明我司告警服务还不太稳定 :)
第一次出现该问题时, 我的处理方式是: 先删了 /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, 有了以下结果:
- [[email protected] shangtongdai-content-util]# ls -lah contentutil.log
- -rw-r--r--. 1 root root 7.6G Nov 7 19:36 contentutil.log
- [[email protected] 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 位置的地方
- [[email protected] ~]# 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
- [[email protected] ~]# ls -l sparse-file
- -rw-r--r--. 1 root root 5242880 Nov 8 11:32 sparse-file
- [[email protected] ~]# ls -ls sparse-file
- 0 -rw-r--r--. 1 root root 5242880 Nov 8 11:32 sparse-file
- // 而 sparse-file 占用的存储空间为 0 个 block
- [[email protected] ~]# du sparse-file
- 0 sparse-file
- [[email protected] ~]# 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, 然后改变文件指针到 1024102410(相当于文件长度这个字段变大了),gcc 编译后运行结果文件详情如下:
- [[email protected] ~]# ls -ls filetest.log
- 8 -rw-------. 1 root root 10485772 Nov 9 17:45 filetest.log
- [[email protected] ~]# du filetest.log
- 8 filetest.log
- [[email protected] ~]# du -h filetest.log
- 8.0K filetest.log
- [[email protected] ~]# ls -lh filetest.log
- -rw-------. 1 root root 11M Nov 9 17:45 filetest.log
- [[email protected] ~]# 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, 再加上 10241024*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 环境高级编程》这本书中有讲 (之前走马观花看过不少, 咋对稀疏文件等一点印象都木有!)
以上内容若有不清楚或不正确的地方, 还望大家指出, 感谢.
说明 1: 题图来源于 SkyPixel.
说明 2: 此文旧文整理重发, 文中有链接其他参考资料可点击阅读原文了解.
精
一个由跨平台产生的浮点数 bug | 有你意想不到的结果.
彩
RSA 算法及一种 "旁门左道" 的 *** 方式.
推
震惊! 阿里的程序员也不过如此, 竟被一个简单的 SQL 查询难住.
荐
面了 7 轮 Google, 最终还是逃不脱被挂的命运.
来源: http://www.bubuko.com/infodetail-3653210.html