磁盤(pán)又雙叒叕滿(mǎn)了,怎么辦?
背景
大家好,我是石頭哥。
題目你可能讀不全(沒(méi)事,俺也一樣),特此補(bǔ)充拼音!
[yòu shuāng ruò zhuó]
又雙叒叕
最近有讀者遇到本文一個(gè)神奇的問(wèn)題,來(lái)詢(xún)問(wèn)我為什么。我看了下,確實(shí)是非常經(jīng)典的坑,我在剛畢業(yè)那會(huì)也遇到過(guò),之前文章也分享過(guò),這次重新整理編輯下,再分享給大家。
可以評(píng)論區(qū)說(shuō)說(shuō),你有遇到過(guò)這個(gè)坑嗎?
difference-between-du-and-ls
知道為什么會(huì)有上面的結(jié)果嗎?什么又是稀疏文件?這篇文章將為你揭秘。
磁盤(pán)滿(mǎn)告警
某天收到的自動(dòng)告警短信或者郵件告訴我某機(jī)器上的磁盤(pán)滿(mǎn)了,趕緊登錄機(jī)器查看。
其實(shí),這都應(yīng)該定時(shí)巡檢自動(dòng)化處理的。
第一次出現(xiàn)該問(wèn)題時(shí), 我的處理方式是: 先刪了 /tmp/ 目錄, 空閑出部分空間, 然后檢查下幾個(gè)常用的用戶(hù)目錄。
最終發(fā)現(xiàn)某服務(wù)A的日志文件(contentutil.log)占用了好幾個(gè)十個(gè)大G,詢(xún)問(wèn)相關(guān)開(kāi)發(fā)人員后確定該日志文件不需要壓縮備份, 可直接刪除,于是 rm contentutil.log 之后就天真地認(rèn)為萬(wàn)事大吉了...
rm 文件后,磁盤(pán)空間就釋放了嗎?
磁盤(pán)滿(mǎn)告警,又來(lái)了
然而,大約xx天后,發(fā)現(xiàn)該機(jī)器磁盤(pán)又滿(mǎn)了,驚呼奇怪咋這么快又滿(mǎn)了。
最終發(fā)現(xiàn)是上次 rm contentutil.log 后, 占用好幾十大G的 contentutil.log 一直被服務(wù)A的進(jìn)程打開(kāi)了,rm 后空間并沒(méi)有釋放。
rm 其實(shí)是刪除該文件名到文件真正保存到磁盤(pán)位置的鏈接,此時(shí)該文件句柄還被服務(wù)A打開(kāi),因此對(duì)應(yīng)的磁盤(pán)空間并沒(méi)有被系統(tǒng)回收。
其實(shí)可以理解為 GC 里面的引用計(jì)數(shù), rm 只是減少了引用計(jì)數(shù),并沒(méi)有真正的進(jìn)行釋放,當(dāng)引用計(jì)數(shù)為0的時(shí)候,OS 內(nèi)核才會(huì)釋放空間,供其他進(jìn)程使用。
所以當(dāng)A進(jìn)程停止(文件句柄的引用計(jì)數(shù)會(huì)變?yōu)?)或者重啟后,占用的存儲(chǔ)空間才被釋放(從某種程度上講說(shuō)明該服務(wù)一直很穩(wěn)定, 可以連續(xù)跑很久不出故障~ 微笑臉)。
tip: 如果不知道具體進(jìn)程或文件名的話(huà):lsof | grep deleted,這樣會(huì)查找所有被刪除的但是文件句柄沒(méi)有釋放的文件和相應(yīng)的進(jìn)程,然后再kill掉進(jìn)程或者重啟進(jìn)程即可。
后來(lái),白老板告知可以用修改文件內(nèi)容的方式在不用重啟進(jìn)程的情況下釋放空間。
下面這個(gè)技巧,劃重點(diǎn):
- echo "" > filename.log
du vs ls
前兩天該問(wèn)題又出現(xiàn)了,該服務(wù)A的日志文件(contentutil.log)占用了約7.6G。
應(yīng)該對(duì)服務(wù)日志做 log rotate。
這一次學(xué)聰明了,直接用echo 'hello' > contentutil.log, 然后 df 確認(rèn)磁盤(pán)空間確實(shí)已經(jīng)釋放,心想著這次可以 Happy 了,突然手賤執(zhí)行了下 ls 和 du, 有了以下結(jié)果:
- [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
反正我看到這樣的結(jié)果是百思不得其解, 如果你已經(jīng)明確為什么會(huì)產(chǎn)生這樣的結(jié)果呢?
可以明確的是, 這里的 ls 和 du 結(jié)果肯定代表不同的含義,在查閱相關(guān)資料和咨詢(xún)強(qiáng)大的票圈后了解到, 這大概與文件空洞和稀疏文件(holes in 'sparse' files)相關(guān).
ls 的結(jié)果是 apparent sizes, 我的理解是文件長(zhǎng)度,就類(lèi)似文件系統(tǒng)中 file 這個(gè)數(shù)據(jù)結(jié)構(gòu)中的定義文件長(zhǎng)度的這個(gè)字段;
du 的結(jié)果 disk usage,即真正占用存儲(chǔ)空間的大小,且默認(rèn)度量單位是 block。
apparent sizes 和 disk usage 說(shuō)法摘自 man du 中的 --apparent-size 部分。
給出一個(gè)具體的示例:
- // 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 內(nèi)容僅僅包含一個(gè)字母"a", 文件長(zhǎng)度為1個(gè)字節(jié), 前面的 8 為占用的存儲(chǔ)空間 8 個(gè) block, (ls -s 的結(jié)果跟 du 的結(jié)果等價(jià), 都是實(shí)際占用磁盤(pán)的空間)。
為什么1個(gè)字節(jié)的文件需要占用8個(gè) block 呢, 可以這樣理解, block 為磁盤(pán)存儲(chǔ)的基本的單位,方便磁盤(pán)尋址等(這里說(shuō)的基本單位應(yīng)該是磁盤(pán)物理結(jié)構(gòu)單位例如一個(gè)扇區(qū)/柱面等,對(duì)應(yīng)一個(gè)物理單位)。
而此處的block可以理解為一個(gè)邏輯單位, 且一個(gè)文件除了包括數(shù)據(jù)外, 還需要存儲(chǔ)描述此文件的其他信息, 因此包含1個(gè)字節(jié)的文件實(shí)際在磁盤(pán)中占用的存儲(chǔ)空間不止1個(gè)字節(jié)。
這里借用最近超火的一篇文章的圖示來(lái)解釋?zhuān)?/p>
不得不說(shuō),這篇 “0.2 秒居然復(fù)制了100G文件?” 文章這動(dòng)圖畫(huà)得真好,火是有原因的。不過(guò),遺憾(諷刺)的是最開(kāi)始的原文竟然找不到了(后補(bǔ)充:源自奇伢云存儲(chǔ),鏈接見(jiàn)評(píng)論),各個(gè)文章轉(zhuǎn)載的時(shí)候,都沒(méi)注原文。
磁盤(pán)文件管理基本單位-block
然后讀寫(xiě)的時(shí)候,都用另外一個(gè)結(jié)構(gòu)來(lái)存儲(chǔ)對(duì)應(yīng)的 block 信息。
文件系統(tǒng)inode 和 block 區(qū)
默認(rèn)情況下, Mac中1個(gè)邏輯 block 中是 512 字節(jié), 因此 du -h 結(jié)果是 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 的結(jié)果應(yīng)該比 du 的結(jié)果更小(都指用默認(rèn)的參數(shù)執(zhí)行, 調(diào)整參數(shù)可使其表達(dá)含義相同), 然而上面跑服務(wù) A 的機(jī)器上 contentutil.log 的對(duì)比結(jié)果是 7.6G vs. 2.3M, 仍然無(wú)法理解了。
稀疏文件
沿著 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是一個(gè)稀疏文件, 雖然其文件長(zhǎng)度很大, 到7.6G了, 然而其中包含大量的holes并不占用實(shí)際的存儲(chǔ)空間。
下面用一個(gè)具體的例子來(lái)復(fù)現(xiàn)以上遇到的問(wèn)題。注意以下例子為 Linux version 2.6.32 (Red Hat 4.4.7)中運(yùn)行結(jié)果, 且在 Mac 中并不能復(fù)現(xiàn)(后文有指出為什么我的Mac不能復(fù)現(xiàn))。
- // 從標(biāo)準(zhǔn)輸入中讀取 count=0 個(gè)block, 輸出到 sparse-file 中,
- // 一個(gè) block 的大小為1k(bs=1k), 輸出時(shí)先將寫(xiě)指針移動(dòng)到 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
- // 所以此時(shí)的文件長(zhǎng)度為: 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 占用的存儲(chǔ)空間為 0 個(gè) block
- [root@localhost ~]# du sparse-file
- 0 sparse-file
- [root@localhost ~]# du -h sparse-file
- 0 sparse-file
此時(shí)若用 vim 打開(kāi)該文件, 用二進(jìn)制形式查看 (tip :%!xxd 可以更改當(dāng)前文件顯示為2進(jìn)制形式), 能看到里面的內(nèi)容全是0. 或者直接用od命令查看2進(jìn)制.
- // vim 二進(jìn)制查看
- 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
實(shí)際上, Sparse 文件是并不占用磁盤(pán)存儲(chǔ)空間的, 那為什么能看到文件里面包含很多0? 因?yàn)楫?dāng)在讀取稀疏文件的時(shí)候, 文件系統(tǒng)根據(jù)文件的 metadata(就是前面所指描述文件的這個(gè)數(shù)據(jù)結(jié)構(gòu))自動(dòng)用0填充[ref Wiki];
Wiki上還說(shuō),現(xiàn)代的不少文件系統(tǒng)都支持 Sparse 文件, 包括 Unix 及其變種和 NTFS, 然而Apple File System(APFS)不支持, 因此我在我的 Mac 上用 du 查看占用空間與 ls 的結(jié)果一致。
- // 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 等命令創(chuàng)建稀疏文件, 也有同學(xué)用 c 代碼實(shí)現(xiàn)了相同的功能。
其實(shí)就是寫(xiě)文件的時(shí)候, 改變下當(dāng)前文件寫(xiě)指針,前面遇到的問(wèn)題就應(yīng)該類(lèi)似。
- #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"寫(xiě)入 filetest.log, 然后改變文件指針到1024*1024*10(相當(dāng)于文件長(zhǎng)度這個(gè)字段變大了), gcc 編譯后運(yùn)行結(jié)果文件詳情如下:
- [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
解釋下結(jié)果: 文件長(zhǎng)度應(yīng)該是 "hello" 加上 "\n" 共6個(gè)字節(jié)*2 = 12, 再加上1024*1024*10個(gè)字節(jié), 即為ls產(chǎn)生的結(jié)果10485772個(gè)字節(jié)約11M。
而du的結(jié)果為8個(gè)block也為8k(這臺(tái)機(jī)器上的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)
總結(jié)
總結(jié)一下: 出現(xiàn)以上問(wèn)題說(shuō)明自己對(duì)一些基礎(chǔ)掌握得尚不牢固, 比如
rm 某文件后, 文件占用的磁盤(pán)空間并不是立即釋放,而是其句柄沒(méi)有被任意一個(gè)進(jìn)程引用時(shí)才回收;
ls/du 命令結(jié)果的具體含義;
稀疏文件。