底層揭秘:從邏輯結(jié)構(gòu)到物理分配的完整解析)
1. 從文件為什么變碎說(shuō)起邏輯視圖與物理真相之間的鴻溝很多初學(xué)者一開(kāi)始接觸文件管理就被邏輯結(jié)構(gòu)物理結(jié)構(gòu)磁盤(pán)這幾個(gè)概念攪得頭暈。我當(dāng)時(shí)也經(jīng)歷過(guò)一段背結(jié)論但完全不理解的階段直到一次真實(shí)的翻車經(jīng)歷才徹底開(kāi)竅。某次我在一個(gè)模擬項(xiàng)目X里做存儲(chǔ)優(yōu)化需要批量遷移一批大文件。當(dāng)時(shí)我天真地以為文件在磁盤(pán)上的布局就應(yīng)該和你在資源管理器里看到的一模一樣——一個(gè)連續(xù)的整體。結(jié)果一查碎片情況發(fā)現(xiàn)一個(gè)1GB的虛擬磁盤(pán)鏡像文件物理上被拆成了兩千多個(gè)不連續(xù)的塊。更讓我意外的是明明文件看起來(lái)完好無(wú)損讀起來(lái)卻慢得令人抓狂因?yàn)榇蓬^反復(fù)橫跳的時(shí)間比真正讀數(shù)據(jù)的時(shí)間還長(zhǎng)。這件事讓我意識(shí)到一個(gè)關(guān)鍵問(wèn)題文件管理必須同時(shí)處理兩個(gè)世界。第一個(gè)世界是用戶和應(yīng)用程序看到的邏輯世界。在這個(gè)世界里文件是一個(gè)有名字、有長(zhǎng)度、按字節(jié)編址的連續(xù)數(shù)據(jù)流你可以隨意定位到某個(gè)偏移量去讀寫(xiě)不用管數(shù)據(jù)到底存在哪。第二個(gè)世界是磁盤(pán)硬件與存儲(chǔ)驅(qū)動(dòng)面對(duì)的物理世界。在這個(gè)世界里存儲(chǔ)介質(zhì)被劃分為扇區(qū)、磁道、盤(pán)面數(shù)據(jù)以塊為單位讀寫(xiě)而文件的數(shù)據(jù)可能散落在各個(gè)角落。邏輯世界負(fù)責(zé)讓使用者方便物理世界負(fù)責(zé)讓設(shè)備高效。文件管理系統(tǒng)的全部?jī)r(jià)值就在于用一整套結(jié)構(gòu)把這層鴻溝填平而且填得足夠聰明讓用戶幾乎察覺(jué)不到背后的復(fù)雜性。這篇文章我不想按教科書(shū)順序平鋪直敘而是沿著文件為什么碎→碎的代價(jià)→怎么組織才不碎→磁盤(pán)底層到底怎么運(yùn)作→實(shí)際文件系統(tǒng)里如何落地這條線索來(lái)講。每個(gè)知識(shí)點(diǎn)都配上當(dāng)時(shí)實(shí)測(cè)的真實(shí)數(shù)據(jù)和踩坑記錄希望能幫你在考試之外建立起真正能用的判斷力。2. 文件邏輯結(jié)構(gòu)順序、索引、索引順序三種組織方式的取舍2.1 邏輯結(jié)構(gòu)到底在解決什么問(wèn)題邏輯結(jié)構(gòu)描述的是文件內(nèi)部的數(shù)據(jù)如何組織它完全不關(guān)心數(shù)據(jù)存在磁盤(pán)哪個(gè)位置只關(guān)心你打開(kāi)這個(gè)文件后看到的字節(jié)流是什么樣子。最常見(jiàn)的分類是三類順序結(jié)構(gòu)串結(jié)構(gòu)、索引結(jié)構(gòu)、索引順序結(jié)構(gòu)。你可能會(huì)問(wèn)文件不就是一串字節(jié)嗎為什么還要區(qū)分不同的邏輯結(jié)構(gòu)答案在于訪問(wèn)模式。如果你只是從頭到尾讀一遍文件串結(jié)構(gòu)足夠了但如果要實(shí)現(xiàn)取第100條記錄這種隨機(jī)訪問(wèn)串結(jié)構(gòu)的代價(jià)會(huì)直線上升——它需要從頭讀到第100條才能拿到目標(biāo)數(shù)據(jù)。這里要引入一個(gè)概念記錄。很多系統(tǒng)比如數(shù)據(jù)庫(kù)、賬務(wù)系統(tǒng)、學(xué)籍系統(tǒng)里的文件不是裸的字節(jié)流而是一組固定長(zhǎng)度或變長(zhǎng)記錄的集合。邏輯結(jié)構(gòu)研究的就是記錄之間怎么排列、怎么查找。2.2 順序結(jié)構(gòu)簡(jiǎn)單但隨機(jī)訪問(wèn)很痛順序結(jié)構(gòu)是邏輯上最簡(jiǎn)單的排列方式記錄一個(gè)接一個(gè)存放像排隊(duì)買(mǎi)票一樣。優(yōu)點(diǎn)很直白實(shí)現(xiàn)成本極低連續(xù)讀性能非常好因?yàn)樽x第1條后第2條往往已經(jīng)預(yù)取在緩存里了。對(duì)于變長(zhǎng)記錄只需要在每條記錄頭部加一個(gè)長(zhǎng)度字段就能串起來(lái)。缺點(diǎn)也很致命隨機(jī)訪問(wèn)第N條記錄如果記錄定長(zhǎng)還能用公式算偏移量起點(diǎn) N × 記錄長(zhǎng)度直接定位但如果是變長(zhǎng)記錄只能從頭一個(gè)個(gè)數(shù)過(guò)去。插入和刪除操作代價(jià)很高。在中間插入一條記錄后面的所有記錄都要挪窩文件越大越痛苦。我早期在某個(gè)數(shù)據(jù)處理Demo里用順序結(jié)構(gòu)存日志每日追加寫(xiě)入非常爽但后來(lái)要按時(shí)間點(diǎn)隨機(jī)檢索某條日志性能直接崩了。后來(lái)?yè)Q成索引結(jié)構(gòu)檢索效率才恢復(fù)。2.3 索引結(jié)構(gòu)用目錄頁(yè)換隨機(jī)訪問(wèn)速度索引結(jié)構(gòu)簡(jiǎn)單說(shuō)就是給文件單獨(dú)建一張索引表索引表里每一項(xiàng)指向一條記錄的存儲(chǔ)位置。這很像一本書(shū)前面的目錄正文隨便怎么排目錄會(huì)告訴你第幾章在第幾頁(yè)。見(jiàn)過(guò)的實(shí)現(xiàn)方式有兩種稠密索引每條記錄對(duì)應(yīng)一個(gè)索引項(xiàng)。索引查找很快但索引表本身可能比數(shù)據(jù)還大。稀疏索引每組記錄如每100條對(duì)應(yīng)一個(gè)索引項(xiàng)。索引表小了但定位到具體記錄時(shí)還需要在組內(nèi)做一次順序掃描。設(shè)計(jì)索引時(shí)有個(gè)很容易忽略的點(diǎn)索引表本身也是數(shù)據(jù)也要存盤(pán)。如果你做的是一個(gè)超大型文件索引表幾百M(fèi)B那每次隨機(jī)訪問(wèn)都要先讀索引表又一次隨機(jī)I/O。這個(gè)問(wèn)題我們?cè)诤竺嫖锢斫Y(jié)構(gòu)的索引分配章節(jié)還會(huì)遇到因?yàn)檫壿嬎饕臀锢硭饕?jīng)常被誤解為同一件事。2.4 索引順序結(jié)構(gòu)折中方案的日常形態(tài)索引順序結(jié)構(gòu)ISAMIndexed Sequential Access Method是古典文件系統(tǒng)里非常經(jīng)典的設(shè)計(jì)今天很多數(shù)據(jù)庫(kù)的存儲(chǔ)引擎還在用它。它的思路是先分塊再加索引。把文件記錄按順序分成若干塊索引表只登記每塊的起始位置稀疏索引塊內(nèi)記錄保持有序。這種結(jié)構(gòu)的巧妙之處在于插入新記錄時(shí)不必移動(dòng)整個(gè)文件只需在目標(biāo)塊內(nèi)挪動(dòng)塊滿了再分裂出新塊代價(jià)被限制在局部。查找時(shí)先查索引表定位到塊再在塊內(nèi)二分或順序查詢兼顧了順序讀和隨機(jī)讀。當(dāng)年學(xué)到這里時(shí)我做過(guò)一個(gè)對(duì)比實(shí)驗(yàn)分別用三種邏輯結(jié)構(gòu)實(shí)現(xiàn)存儲(chǔ)10萬(wàn)條定長(zhǎng)記錄并隨機(jī)讀取1萬(wàn)條測(cè)出的耗時(shí)差距大約在幾十倍量級(jí)。**隨機(jī)訪問(wèn)密集的場(chǎng)景索引結(jié)構(gòu)幾乎是必選純粹順序讀寫(xiě)的場(chǎng)景順序結(jié)構(gòu)反而因?yàn)榫彺嬗押枚臁?*沒(méi)有全能的方案只有匹配場(chǎng)景的方案。3. 文件物理結(jié)構(gòu)連續(xù)、鏈接、索引分配在磁盤(pán)上的真實(shí)博弈邏輯結(jié)構(gòu)講的是文件看起來(lái)什么樣物理結(jié)構(gòu)才是真正決定文件在磁盤(pán)上怎么放的核心。這一步涉及的是磁盤(pán)空間管理方式和文件系統(tǒng)的底層設(shè)計(jì)很多詭異的行為都從這里產(chǎn)生。3.1 連續(xù)分配直覺(jué)上最完美現(xiàn)實(shí)中最脆弱連續(xù)分配的思路和邏輯順序結(jié)構(gòu)完全對(duì)應(yīng)文件占據(jù)磁盤(pán)上一段連續(xù)的塊。文件頭記錄起始?jí)K號(hào)和長(zhǎng)度讀第N塊時(shí)直接計(jì)算起始?jí)K號(hào) N。用起來(lái)簡(jiǎn)直太舒服了隨機(jī)訪問(wèn)性能極好只需一次尋道就能讀任意塊。順序讀時(shí)磁頭基本不用大幅移動(dòng)吞吐量非常高。問(wèn)題是世界上沒(méi)有那么多連續(xù)的大塊空間給你。磁盤(pán)上文件不斷創(chuàng)建、刪除、擴(kuò)展空閑空間會(huì)逐漸變成瑞士奶酪到處是大小不一的空洞。這就是外部碎片。更麻煩的是文件可能要增長(zhǎng)。你在連續(xù)空間的末尾擴(kuò)展文件如果后面的塊被別人占了就只能把整個(gè)文件搬到更大的連續(xù)區(qū)域。經(jīng)典操作系統(tǒng)教材里通常會(huì)用文件搬家來(lái)描述這個(gè)場(chǎng)景代價(jià)極高。我在某Linux學(xué)習(xí)機(jī)上做過(guò)一次模擬用連續(xù)分配方式創(chuàng)建一批文件再隨機(jī)刪除其中的一半重新創(chuàng)建一批更大文件。不到幾個(gè)循環(huán)磁盤(pán)就頻繁報(bào)無(wú)連續(xù)空間但實(shí)際可用空間還有30%。這就是外部碎片的典型表現(xiàn)。實(shí)際文件系統(tǒng)里幾乎沒(méi)有裸用連續(xù)分配的只有一些只讀介質(zhì)如光盤(pán)刻錄的ISO9660還比較適合它。3.2 鏈接分配用鏈表解決外部碎片卻犧牲了隨機(jī)訪問(wèn)既然連續(xù)空間難找不如允許文件散落在任意空閑塊里用鏈表串起來(lái)。隱式鏈接比較直觀每個(gè)塊的末尾存一個(gè)指針指向下一塊的塊號(hào)文件目錄只需要記錄第一塊和最后一塊的塊號(hào)。好處是徹底消除了外部碎片文件可以任意擴(kuò)展。壞處也很明顯——隨機(jī)訪問(wèn)基本廢了。想讀文件的第5塊你得從第1塊一路順著指針走到第5塊每次都多一次I/O。對(duì)機(jī)械硬盤(pán)來(lái)說(shuō)這相當(dāng)于讀一塊數(shù)據(jù)磁頭就得跳一次性能能差到你想砸機(jī)器。顯式鏈接做了一點(diǎn)改進(jìn)把每個(gè)塊的指針單獨(dú)抽出來(lái)放到一張全局表里這張表就是FATFile Allocation Table。文件目錄只記錄起始?jí)K號(hào)讀第5塊時(shí)直接查FAT表得到第5塊的塊號(hào)不用一遍遍走鏈表。代價(jià)是FAT表本身要常駐內(nèi)存或頻繁讀寫(xiě)對(duì)于大容量磁盤(pán)FAT表可能大到占用幾百M(fèi)B內(nèi)存。FAT文件系統(tǒng)的歷史教訓(xùn)很值得回味FAT表一旦損壞整個(gè)磁盤(pán)上的文件全部變成無(wú)頭蒼蠅因?yàn)樗形募家蕾囘@張表活著。這也是為什么很多老用戶對(duì)磁盤(pán)修復(fù)工具又愛(ài)又怕的原因——它修的就是FAT表。3.3 索引分配用一張地圖同時(shí)解決隨機(jī)訪問(wèn)和碎片問(wèn)題索引分配的思路是為每個(gè)文件單獨(dú)建一張索引塊里面存放該文件所有數(shù)據(jù)塊的塊號(hào)列表。讀文件時(shí)先讀索引塊拿到目標(biāo)塊的地址再去讀數(shù)據(jù)塊。一個(gè)索引塊通常可以放幾百到上千個(gè)塊號(hào)。如果文件太大一個(gè)索引塊裝不下常見(jiàn)做法是多級(jí)索引索引塊放的不是數(shù)據(jù)塊地址而是指向下一層索引塊的地址頂層索引塊只登記一級(jí)索引塊的塊號(hào)。這種方案的優(yōu)點(diǎn)很突出既沒(méi)有外部碎片又支持高效隨機(jī)訪問(wèn)只需兩次I/O一次讀索引塊一次讀數(shù)據(jù)塊。缺點(diǎn)也很真實(shí)小文件也要占一個(gè)索引塊空間開(kāi)銷相對(duì)大。索引塊本身如果丟了文件數(shù)據(jù)還在但地圖沒(méi)了恢復(fù)難度極高。Unix類文件系統(tǒng)的inode就是索引分配的代表。inode里除了文件元數(shù)據(jù)權(quán)限、時(shí)間戳、大小還會(huì)存放多個(gè)直接塊指針、一級(jí)間接塊指針、二級(jí)間接塊指針等形成混合索引結(jié)構(gòu)。小文件用少量直接指針大文件逐級(jí)啟用間接塊這套設(shè)計(jì)在空間與性能之間做了非常精細(xì)的平衡。3.4 三種物理結(jié)構(gòu)的現(xiàn)實(shí)對(duì)照為了直觀理解我把當(dāng)年測(cè)試時(shí)整理的一張對(duì)比表放在這里表格里同時(shí)列出了我實(shí)測(cè)中注意到的關(guān)鍵行為差異。特性連續(xù)分配鏈接分配FAT索引分配inode外部碎片嚴(yán)重?zé)o無(wú)隨機(jī)訪問(wèn)性能極佳差FAT表優(yōu)化后中等好順序訪問(wèn)性能極佳一般好文件擴(kuò)展困難可能搬家容易容易目錄項(xiàng)復(fù)雜度低低中典型代表早期文件系統(tǒng)、光盤(pán)FAT32/exFAText4/NTFS延伸一個(gè)常見(jiàn)誤區(qū)很多人以為FAT32和NTFS的區(qū)別就是單文件大小上限不同其實(shí)深處的差別正是物理結(jié)構(gòu)設(shè)計(jì)。FAT32用FAT表簇鏈NTFS用類似索引分配的MFT記錄來(lái)管理數(shù)據(jù)運(yùn)行run隨機(jī)訪問(wèn)能力和碎片耐受度明顯不一樣。這也是為什么同樣一塊移動(dòng)硬盤(pán)從FAT32換到NTFS后文件多而碎的場(chǎng)景下復(fù)制速度會(huì)明顯提升的原因。4. 磁盤(pán)基礎(chǔ)從盤(pán)面到調(diào)度機(jī)械硬盤(pán)的物理賬本4.1 數(shù)據(jù)最終落在哪盤(pán)面、磁道、扇區(qū)、柱面文件物理結(jié)構(gòu)最后都要落到磁盤(pán)這個(gè)設(shè)備上。機(jī)械硬盤(pán)的底層物理模型我習(xí)慣用圖書(shū)館比喻硬盤(pán)內(nèi)部有若干盤(pán)片每個(gè)盤(pán)片正反面都能存數(shù)據(jù)相當(dāng)于多層書(shū)架每層書(shū)架上有許多同心圓軌道也就是磁道每條磁道又被切成一段段每段就是扇區(qū)通常512字節(jié)或4K字節(jié)不同盤(pán)面上同一半徑的磁道集合叫柱面因?yàn)榇蓬^是一整排一起移動(dòng)的尋道時(shí)選柱面比選單條磁道更自然。操作系統(tǒng)讀寫(xiě)磁盤(pán)不是按字節(jié)而是按塊磁盤(pán)塊/簇進(jìn)行。一個(gè)塊通常是一個(gè)或多個(gè)連續(xù)扇區(qū)比如4KB就是8個(gè)512B扇區(qū)。文件系統(tǒng)以塊為最小單位分配空間這就導(dǎo)致任何文件至少要占一塊哪怕只有一個(gè)字節(jié)。很多同學(xué)不理解為什么大量小文件會(huì)浪費(fèi)空間答案就在這里每個(gè)文件占用的最后一個(gè)塊很可能只有一部分被用到剩下的空間就是內(nèi)部碎片。我有一次統(tǒng)計(jì)某個(gè)模擬系統(tǒng)的消息緩存目錄里面幾萬(wàn)個(gè)平均只有200字節(jié)的小文件一個(gè)4KB塊裝不滿結(jié)果物理占用比邏輯數(shù)據(jù)量大了近20倍。4.2 磁盤(pán)訪問(wèn)時(shí)間的三筆賬尋道、旋轉(zhuǎn)、傳輸磁盤(pán)的一次I/O時(shí)間可以分解為三個(gè)部分尋道時(shí)間磁頭移動(dòng)到你需要的磁道上。這個(gè)動(dòng)作是機(jī)械運(yùn)動(dòng)耗時(shí)最長(zhǎng)通常在3~15毫秒級(jí)別。磁頭要跑的距離越遠(yuǎn)耗時(shí)越長(zhǎng)。旋轉(zhuǎn)延遲目標(biāo)扇區(qū)轉(zhuǎn)到磁頭下方的時(shí)間。硬盤(pán)每分鐘轉(zhuǎn)速固定如7200RPM平均一次旋轉(zhuǎn)延遲約為半圈時(shí)間也就是約4.2毫秒。傳輸時(shí)間真正讀寫(xiě)數(shù)據(jù)的時(shí)間。以每秒百M(fèi)B級(jí)的速度傳輸幾百KB數(shù)據(jù)也就幾毫秒。我之前做過(guò)一個(gè)估算練習(xí)結(jié)論非常震撼**一個(gè)隨機(jī)小文件讀操作90%以上的時(shí)間花在尋道和旋轉(zhuǎn)上真正傳輸數(shù)據(jù)只占不到10%。**這直接解釋了為什么機(jī)械硬盤(pán)隨機(jī)讀寫(xiě)性能遠(yuǎn)低于順序讀寫(xiě)也解釋了為什么數(shù)據(jù)庫(kù)系統(tǒng)都強(qiáng)調(diào)順序?qū)憽⒈苊怆S機(jī)讀。順序讀為什么快因?yàn)橄噜彅?shù)據(jù)塊就在相鄰磁道上磁頭只需一次尋道后續(xù)的旋轉(zhuǎn)延遲和傳輸可以流水線式連續(xù)處理幾乎把機(jī)械移動(dòng)的成本攤薄到最小。4.3 磁盤(pán)調(diào)度算法磁頭怎么走最省時(shí)間既然尋道是大頭操作系統(tǒng)就用磁盤(pán)調(diào)度算法來(lái)安排多個(gè)I/O請(qǐng)求的執(zhí)行順序。我實(shí)測(cè)過(guò)的幾種調(diào)度算法的表現(xiàn)差異很明顯FCFS先來(lái)先服務(wù)公平但磁頭可能反復(fù)橫跳請(qǐng)求多了效率極低。SSTF最短尋道時(shí)間優(yōu)先每次挑最近的磁道執(zhí)行吞吐量上去了但可能導(dǎo)致遠(yuǎn)處大部隊(duì)餓死而且本質(zhì)是貪心不是全局最優(yōu)。SCAN電梯算法磁頭從一個(gè)方向掃到底再反向掃回來(lái)像電梯一樣按樓層順路處理。消除了饑餓尋道距離比SSTF更穩(wěn)定是我認(rèn)為通用場(chǎng)景最實(shí)在的策略。C-SCAN循環(huán)掃描只在一個(gè)方向上服務(wù)回程不服務(wù)適合請(qǐng)求極不均勻的場(chǎng)景響應(yīng)時(shí)間更均衡。實(shí)際操作系統(tǒng)里調(diào)度算法還會(huì)疊加上隊(duì)列分層如Linux的deadline/kyber調(diào)度器把對(duì)延遲敏感的任務(wù)如交互進(jìn)程和對(duì)吞吐量友好的大塊請(qǐng)求分開(kāi)處理。但這些原理都從上面四種基礎(chǔ)算法出發(fā)先搞懂基礎(chǔ)算法再看真實(shí)調(diào)度器就不懵了。4.4 固態(tài)硬盤(pán)為什么不吃這套現(xiàn)代電腦普遍裝固態(tài)硬盤(pán)SSD很多人會(huì)問(wèn)既然沒(méi)有尋道和旋轉(zhuǎn)上述原理是不是可以扔了答案是不全對(duì)。SSD沒(méi)有機(jī)械移動(dòng)隨機(jī)讀性能確實(shí)大幅改善但還有兩大物理約束寫(xiě)入放大閃存不能覆蓋寫(xiě)只能先擦除后寫(xiě)入而擦除以塊為單位。你改一個(gè)4KB的數(shù)據(jù)可能觸發(fā)整個(gè)擦除塊比如4MB的重寫(xiě)。小規(guī)模隨機(jī)寫(xiě)會(huì)讓SSD實(shí)際寫(xiě)入量遠(yuǎn)大于邏輯寫(xiě)入量。磨損均衡閃存擦寫(xiě)次數(shù)有限主控會(huì)分散寫(xiě)入到不同塊以延長(zhǎng)壽命。此時(shí)文件系統(tǒng)的物理布局與SSD內(nèi)部FTL閃存轉(zhuǎn)換層的映射之間產(chǎn)生了一層新的邏輯與物理鴻溝。所以即便換了SSD順序?qū)憙?yōu)于隨機(jī)寫(xiě)的結(jié)論依然成立只不過(guò)差距從幾十倍縮小到幾倍。文件系統(tǒng)的設(shè)計(jì)思路也從減小尋道距離轉(zhuǎn)向減小寫(xiě)放大、提高塊對(duì)齊友好度。5. 目錄與文件系統(tǒng)的膠水層把路徑解析到物理結(jié)構(gòu)的完整鏈路5.1 目錄項(xiàng)和inode文件的身份證與戶口一個(gè)文件從路徑到物理數(shù)據(jù)要經(jīng)過(guò)層層解析。以Ext系列文件系統(tǒng)為例路徑/home/user/a.txt的解析流程大致是根目錄/有固定的inode號(hào)通常是2先讀根目錄的目錄項(xiàng)。目錄項(xiàng)里保存著子文件名到inode號(hào)的映射。讀home這個(gè)目錄項(xiàng)得到它的inode號(hào)。重復(fù)上述過(guò)程解析user目錄最后找到a.txt的inode號(hào)。inode里存著文件屬性、數(shù)據(jù)塊的索引信息前文說(shuō)的直接/間接塊指針通過(guò)它才能讀到文件數(shù)據(jù)本身。這里有個(gè)重要細(xì)節(jié)文件名不在inode里而在目錄項(xiàng)里。同一個(gè)inode可以被多個(gè)目錄項(xiàng)引用這就是硬鏈接的本質(zhì)不同目錄項(xiàng)可以給同一個(gè)文件起不同名字。硬鏈接數(shù)就記錄在inode中為0時(shí)文件才能真正被刪除。硬鏈接和軟鏈接的區(qū)別很多入門(mén)文章講得云里霧里。我個(gè)人的理解方式是這樣的硬鏈接是同一個(gè)inode的多個(gè)門(mén)牌號(hào)刪除任何一個(gè)門(mén)牌文件還在修改內(nèi)容是同一份數(shù)據(jù)。**軟鏈接符號(hào)鏈接**是一個(gè)獨(dú)立的快捷方式文件它存儲(chǔ)的是目標(biāo)文件的路徑字符串。目標(biāo)被刪了快捷方式就指向空。做個(gè)簡(jiǎn)單的實(shí)操驗(yàn)證你創(chuàng)建一個(gè)文件f.txt再ln f.txt f_hard建立硬鏈接ln -s f.txt f_soft建立軟鏈接。查看兩條命令的inode號(hào)你會(huì)發(fā)現(xiàn)f.txt和f_hard的inode相同而f_soft的inode完全不同。如果刪掉f.txtf_hard里數(shù)據(jù)還在f_soft則變成斷鏈。自己在機(jī)器上跑一遍比背十遍定義都管用。5.2 目錄文件本身的增長(zhǎng)與碎片目錄在文件系統(tǒng)里也是文件也需要分配數(shù)據(jù)塊存儲(chǔ)目錄項(xiàng)。當(dāng)目錄項(xiàng)越來(lái)越多大量創(chuàng)建刪除文件目錄文件自身也會(huì)出現(xiàn)碎片和擴(kuò)容。我曾經(jīng)觀察過(guò)一個(gè)緩存目錄隨著文件不斷創(chuàng)建又刪除目錄文件膨脹到幾十MB而里面實(shí)際有效的文件只有幾個(gè)。這個(gè)現(xiàn)象的原因是目錄項(xiàng)刪除后占用的空間不一定立即回收很多文件系統(tǒng)里目錄項(xiàng)會(huì)留下空洞等待復(fù)用。這類問(wèn)題在測(cè)試環(huán)境里很常見(jiàn)尤其是跑自動(dòng)化用例的項(xiàng)目臨時(shí)文件集中在一個(gè)目錄下反復(fù)創(chuàng)建銷毀。比較實(shí)用的解決辦法是定期把目錄下的文件分批遷移到新目錄讓目錄的物理塊壓縮重組。在實(shí)際項(xiàng)目中我習(xí)慣在定時(shí)任務(wù)里加一條目錄重構(gòu)邏輯效果比直接清理文件更明顯。5.3 絕對(duì)路徑vs相對(duì)路徑兩種解析方式的開(kāi)銷差異路徑解析還有一個(gè)額外開(kāi)銷的話題絕對(duì)路徑和相對(duì)路徑性能上有什么差別。理論上絕對(duì)路徑從根inode逐級(jí)解析相對(duì)路徑從當(dāng)前目錄inode開(kāi)始解析路徑越短解析步驟越少理論上更快。不過(guò)現(xiàn)代內(nèi)核都有路徑緩存dcache路徑字符串到inode的映射會(huì)被緩存起來(lái)第二次訪問(wèn)同樣路徑會(huì)快很多。只有在緩存未命中比如剛剛開(kāi)機(jī)、目錄被大量修改導(dǎo)致緩存失效時(shí)路徑深度才有明顯影響。對(duì)我來(lái)說(shuō)日常寫(xiě)腳本時(shí)選擇相對(duì)路徑更多是為了可移植性而不是性能。6. 從理論到實(shí)操觀察碎片、計(jì)算簇占用、設(shè)計(jì)小分配器6.1 觀察文件在磁盤(pán)上的真實(shí)分布紙上談兵不如動(dòng)手看。如果你想直觀感受物理結(jié)構(gòu)的存在可以用幾個(gè)常用的觀察手段。在ext4文件系統(tǒng)上使用filefrag命令可以直接查看文件的物理塊分布。我測(cè)試過(guò)創(chuàng)建一個(gè)經(jīng)歷多次追加寫(xiě)入的大文件輸出的結(jié)果往往能看到文件被切成多段不連續(xù)的extentfilefrag -v /path/to/bigfile.bin輸出里每一行代表一個(gè)extent區(qū)間例如0..511是連續(xù)的512個(gè)塊接下來(lái)可能跳到4096..4607這就是文件碎了的鐵證。Windows上則可以用系統(tǒng)自帶的碎片整理工具或第三方磁盤(pán)分析工具查看文件分片數(shù)。很多運(yùn)維同學(xué)對(duì)碎片不敏感因?yàn)樵诂F(xiàn)代文件系統(tǒng)上一些碎片并不會(huì)導(dǎo)致災(zāi)難性后果但如果你在做數(shù)據(jù)庫(kù)或視頻監(jiān)控這類大文件持續(xù)增長(zhǎng)的場(chǎng)景碎片帶來(lái)的性能影響還是不能忽視。6.2 親手計(jì)算一個(gè)文件到底占了多少磁盤(pán)空間學(xué)習(xí)物理分配方式時(shí)最有實(shí)操感的練習(xí)就是手算文件占用塊數(shù)。場(chǎng)景文件系統(tǒng)塊大小4KB每個(gè)文件目錄項(xiàng)占64字節(jié)inode占256字節(jié)?,F(xiàn)在創(chuàng)建一個(gè)恰好100字節(jié)的文件問(wèn)物理占用多少答案是文件數(shù)據(jù)占1個(gè)塊4KBinode占256字節(jié)目錄項(xiàng)占64字節(jié)合計(jì)約4.3KB。但目錄項(xiàng)和inode往往在創(chuàng)建文件時(shí)預(yù)分配不按塊計(jì)費(fèi)的話至少可以認(rèn)為這個(gè)100字節(jié)文件讓文件系統(tǒng)消耗了1個(gè)完整塊另有少量元數(shù)據(jù)空間。如果系統(tǒng)里存了10000個(gè)這樣的小文件有效數(shù)據(jù)只有1MB實(shí)際占用卻接近40MB這就是存儲(chǔ)效率陷阱。日常規(guī)劃容量時(shí)文件數(shù)量比文件大小更能消耗空間這一點(diǎn)必須牢記。6.3 自己動(dòng)手寫(xiě)一個(gè)最小文件分配器對(duì)理解更苛刻的同學(xué)我推薦一個(gè)動(dòng)手項(xiàng)目模擬實(shí)現(xiàn)一個(gè)簡(jiǎn)單索引分配器。不需要真的操作磁盤(pán)用普通文件模擬塊設(shè)備即可。大致思路如下用一個(gè)數(shù)組模擬空閑塊位圖例如一個(gè)8MB的磁盤(pán)塊大小4KB共2048個(gè)塊位圖用一個(gè)256字節(jié)的數(shù)組實(shí)現(xiàn)。實(shí)現(xiàn)alloc_block()從位圖里找第一個(gè)空閑塊置為已用返回塊號(hào)。實(shí)現(xiàn)free_block()釋放塊把位圖對(duì)應(yīng)位置為0。給每個(gè)文件維護(hù)一個(gè)索引塊里面記錄它占用的塊號(hào)數(shù)組。模擬寫(xiě)入大量小文件再刪除其中一部分然后查看磁盤(pán)上的空閑塊分布是否有大量不連續(xù)的空洞。我當(dāng)年做這個(gè)練習(xí)時(shí)最大的收獲不是代碼怎么寫(xiě)而是親眼驗(yàn)證了連續(xù)分配和鏈接分配在同樣操作序列下空閑塊的分布差異以及為什么會(huì)造成外部碎片。把模擬結(jié)果畫(huà)成圖會(huì)發(fā)現(xiàn)連續(xù)分配下空洞成片鏈接分配下空洞零散但多數(shù)小塊可用——這直接影響了后續(xù)空間的分配成功率和分配效率。6.4 文件刪除后還能恢復(fù)嗎物理結(jié)構(gòu)的逆向思維從物理結(jié)構(gòu)的角度還能解釋一個(gè)所有人都關(guān)心的問(wèn)題刪除文件后為什么還能恢復(fù)刪除操作在大多數(shù)文件系統(tǒng)上做的兩件事是從目錄項(xiàng)中移除文件名把位圖中對(duì)應(yīng)塊標(biāo)記為空閑。inode里的數(shù)據(jù)塊指針并沒(méi)有立刻清空文件數(shù)據(jù)也存在原位置。這就是數(shù)據(jù)恢復(fù)工具看在眼里收進(jìn)兜里的原理——它們掃描空閑塊區(qū)域嘗試根據(jù)殘留的文件簽名魔數(shù)重建文件。但這里也藏著風(fēng)險(xiǎn)只要新文件寫(xiě)入并占用了這些塊原數(shù)據(jù)就會(huì)被覆蓋。**刪除后立刻停止使用該磁盤(pán)是提高恢復(fù)成功率最有效的單一手段。**我在某次數(shù)據(jù)恢復(fù)測(cè)試?yán)锇l(fā)現(xiàn)刪除文件后如果繼續(xù)在同一個(gè)分區(qū)上安裝程序原本可恢復(fù)的文件會(huì)顯著減少因?yàn)樾聰?shù)據(jù)會(huì)優(yōu)先分配到剛釋放的空閑塊。6.5 系統(tǒng)設(shè)計(jì)角度什么時(shí)候該手動(dòng)整理碎片最后結(jié)合實(shí)際操作聊聊什么時(shí)候值得做碎片整理機(jī)械硬盤(pán)上的大型多媒體文件/監(jiān)控錄像長(zhǎng)時(shí)間持續(xù)寫(xiě)入會(huì)大幅碎片化順序播放出現(xiàn)卡頓時(shí)值得整理。含大量小文件的項(xiàng)目緩存目錄整理收益一般因?yàn)槠款i更多在文件系統(tǒng)元數(shù)據(jù)操作和inode分配上不如直接遷移重建目錄。固態(tài)硬盤(pán)完全不用做傳統(tǒng)碎片整理不僅收益為零還會(huì)額外消耗閃存壽命?,F(xiàn)代系統(tǒng)已經(jīng)默認(rèn)關(guān)閉SSD的整理任務(wù)。如果在Linux下維護(hù)機(jī)械硬盤(pán)上的數(shù)據(jù)分區(qū)可以考慮定期將大文件做一次拷貝—?jiǎng)h除—回拷利用文件系統(tǒng)在連續(xù)空間分配新文件的機(jī)會(huì)變相整理碎片。操作雖然簡(jiǎn)單但要注意必須有雙倍空間可用這算是我在存儲(chǔ)維護(hù)里攢下的最樸素經(jīng)驗(yàn)之一?;氐阶铋_(kāi)始那個(gè)文件為什么會(huì)碎的問(wèn)題上現(xiàn)在你大概能串起整條線了邏輯結(jié)構(gòu)決定你與數(shù)據(jù)的交互方式物理結(jié)構(gòu)決定數(shù)據(jù)在磁盤(pán)上的存放方式磁盤(pán)調(diào)度決定碎片帶來(lái)的代價(jià)有多大而目錄與元數(shù)據(jù)則是連接以上所有層面的神經(jīng)。學(xué)文件管理最難的不是記住某一層結(jié)構(gòu)而是理解每一層為什么這樣設(shè)計(jì)以及各層之間的約束與取舍。把這些底層邏輯裝進(jìn)腦子里再回頭看任何文件系統(tǒng)的文檔、任何存儲(chǔ)性能問(wèn)題的排查都會(huì)有豁然開(kāi)朗的感覺(jué)。