點擊關(guān)注公眾號,Java干貨及時送達
來源?| OSC開源社區(qū)(ID:oschina2013)
背景是這樣的:他用?cp??拷貝了一個 100 G的文件,竟然一秒不到就拷貝完成了!用?ls??看一把文件,顯示文件確實是 100 G。sh-4.4# ls?-lh
-rw-r--r-- 1?root root 100G Mar 6?12:22?test.txt
sh-4.4# time cp ./test.txt ./test.txt.cp
real?0m0.107s
user 0m0.008s
sys 0m0.085s
一個 SATA 機械盤的寫能力能到 150 M/s (大部分的機械盤都是到不了這個值的)就算非常不錯了,正常情況下,copy 一個 100G 的文件至少要 682 秒 ( 100 G/ 150 M/s ),也就是 11 分鐘。實際情況卻是?cp?一秒沒到就完成了工作,驚呆了,為啥呢?更詭異的是:他的文件系統(tǒng)只有 40 G,為啥里面會有一個 100 G的文件呢?我讓他先用?du?命令看一下,卻只有 2M ,根本不是100G,這是怎么回事?sh-4.4# du -sh?./test.txt
2.0M ./test.txt
sh-4.4# stat?./test.txt
??File: ./test.txt
??Size: 107374182400?Blocks: 4096???????IO?Block: 4096???regular?file
Device: 78h/120d?Inode: 3148347?????Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-03-13?12:22:00.888871000?+0000
Modify: 2021-03-13?12:22:46.562243000?+0000
Change: 2021-03-13?12:22:46.562243000?+0000
?Birth: -
- Size 為 107374182400(知識點:單位是字節(jié)),也就是 100G ;
- Blocks 這個指標顯示為 4096(知識點:一個 Block 的單位固定是 512 字節(jié),也就是一個扇區(qū)的大小),這里表示為 2M;
- Size 表示的是文件大小,這個也是大多數(shù)人看到的大小;
同事問道:“文件大小和實際物理占用,這兩個竟然不是相同的概念 !為什么是這樣??”??“看來,我們必須得深入文件系統(tǒng)才能理解了,來,我給你好好講講。”文件系統(tǒng)聽起來很高大上,通俗話就用來存數(shù)據(jù)的一個容器而已,本質(zhì)和你的行李箱、倉庫沒有啥區(qū)別,只不過文件系統(tǒng)存儲的是數(shù)字產(chǎn)品而已。我有一個視頻文件,我把這個視頻放到這個文件系統(tǒng)里,下次來拿,要能拿到我完整的視頻文件數(shù)據(jù),這就是文件系統(tǒng),對外提供的就是存取服務(wù)。存行李的時候,是不是要登記一些個人信息?對吧,至少自己名字要寫上。可能還會給你一個牌子,讓你掛手上,這個東西就是為了標示每一個唯一的行李。取行李的時候,要報自己名字,有牌子的給他牌子,然后工作人員才能去特定的位置找到你的行李劃重點:存的時候必須記錄一些關(guān)鍵信息(記錄ID、給身份牌),取的時候才能正確定位到。回到我們的文件系統(tǒng),對比上面的行李存取行為,可以做個簡單的類比;
上面的對應(yīng)并不是非常嚴謹,僅僅是幫助大家理解文件系統(tǒng)而已,讓大家知道其實文件系統(tǒng)是非常樸實的一個東西,思想都來源于生活。另外,Java 系列面試題和答案全部整理好了,微信搜索Java技術(shù)棧,在后臺發(fā)送:面試,可以在線閱讀。現(xiàn)在思考文件系統(tǒng)是怎么管理空間的?
如果,一個連續(xù)的大磁盤空間給你使用,你會怎么使用這段空間呢?直觀的一個想法,我把進來的數(shù)據(jù)就完整的放進去。這種方式非常容易實現(xiàn),屬于眼前最簡單,以后最麻煩的方式。因為會造成很多空洞,明明還有很多空間位置,但是由于整個太大,形狀不合適(數(shù)據(jù)大小),哪里都放不下。因為你要放一個完整的空間。怎么改進?有人會想,既然整個放不進去,那就剁碎了唄。這里塞一點,那里塞一點,就塞進去了。對,思路完全正確。改進的方式就是切分,把空間按照一定粒度切分。每個小粒度的物理塊命名為 Block,每個 Block 一般是 4K 大小,用戶數(shù)據(jù)存到文件系統(tǒng)里來自然也是要切分,存儲到磁盤上各個角落。圖示標號表示這個完整對象的 Block 的序號,用來復(fù)原對象用的。隨之而來又有一個問題:你光會切成塊還不行,取文件數(shù)據(jù)的時候,還得把它們給組合起來才行。所以,要有一個表記錄文件對應(yīng)所有 Block 的位置,這個表被文件系統(tǒng)稱為inode。推薦一個 Spring Boot 基礎(chǔ)教程及實戰(zhàn)示例:https://www.javastack.cn/categories/Spring-Boot/- 先寫數(shù)據(jù):數(shù)據(jù)先按照 Block 粒度存儲到磁盤的各個位置;
- 再寫元數(shù)據(jù):然后把 Block 所在的各個位置保存起來,即inode(我用一本書來表示);
- 然后讀數(shù)據(jù),構(gòu)造一個完整的文件,給到用戶;
好,我們現(xiàn)在來看看inode,直觀地感受一下:你肯定會意識到:Block數(shù)組只有15個元素,每個Block是4K, 難道一個文件最大只能是 15 * 4K =? 60 K ? ?最簡單的辦法就是:把這個Block數(shù)組長度給擴大!比如我們想讓文件系統(tǒng)最大支持100G的文件,Block數(shù)組需要這么長:(100*1024*1024)/4 =?26214400Block數(shù)組中每一項是4個字節(jié),那就需要(26214400*4)/1024/1024 =?100M?為了支持100G的文件,我們的Block數(shù)組本身就得100M !?并且對每個文件都是如此 !即使這個文件只有1K!?這將是巨大浪費!肯定不能這么干,解決方案就是間接索引,按照約定,把這?15 個槽位分作 4 個不同類別來用:- 前 12 個槽位(也就是 0 - 11 )我們成為直接索引;
直接索引:能存 12 個 block 編號,每個 block 4K,就是?48K,也就是說,48K 以內(nèi)的文件,前 12 個槽位存儲編號就能完全 hold 住。點擊關(guān)注公眾號,Java干貨及時送達
也就是說這里存儲的編號指向的 block 里面存儲的也是 block 編號,里面的編號指向用戶數(shù)據(jù)。一個 block ?4K,每個元素 4 字節(jié),也就是有 1024 個編號位置可以存儲。所以,一級索引能尋址?4M(1024 * 4K)空間 。二級索引是在一級索引的基礎(chǔ)上多了一級而已,換算下來,有了 4M 的空間用來存儲用戶數(shù)據(jù)的編號。所以二級索引能尋址?4G (4M/4 * 4K)?的空間。三級索引是在二級索引的基礎(chǔ)上又多了一級,也就是說,有了 4G 的空間來存儲用戶數(shù)據(jù)的 block 編號。所以二級索引能尋址 4T (4G/4 * 4K) 的空間。所以,在這種文件系統(tǒng)(如ext2)上,通過這種間接塊索引的方式,最大能支撐的文件大小 = 48K + 4M + 4G + 4T ,約等于 4 T。在不超過 12 個數(shù)據(jù)塊的小文件的尋址是最快的,訪問文件中的任意數(shù)據(jù)理論只需要兩次讀盤,一次讀 inode,一次讀數(shù)據(jù)塊。訪問大文件中的數(shù)據(jù)則需要最多五次讀盤操作:inode、一級間接尋址塊、二級間接尋址塊、三級間接尋址塊、數(shù)據(jù)塊。
這樣的文件其實就是稀疏文件, 它的邏輯大小和實際物理空間是不相等的。?所以當(dāng)我們用cp命令去復(fù)制一個這樣的文件時,那肯定迅速就完成了。好,我們再深入思考下,文件系統(tǒng)為什么能做到這一點?- 首先,最關(guān)鍵的是把磁盤空間切成離散的、定長的 block 來管理;
- 然后,通過 inode 能查找到所有離散的數(shù)據(jù)(保存了所有的索引);
- 最后,實現(xiàn)索引塊和數(shù)據(jù)塊空間的后分配;
這三點是層層遞進的。另外,關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù):面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。

關(guān)注Java技術(shù)棧看更多干貨
獲取 Spring Boot 實戰(zhàn)筆記!