由一次磁盘告警引发的“血案”——你知道 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 編譯后運行結果文件詳情如下:
解釋下結果:?文件長度應該是 “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 区别吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kubernetes 将何去何从?
- 下一篇: 医疗保健、零售、金融、制造业……一文带你