硬盤物理結(jié)構(gòu)
硬盤內(nèi)部主要部件為磁盤盤片、傳動手臂、讀寫磁頭和主軸馬達(dá)呼巴。實(shí)際數(shù)據(jù)都是寫在盤片上,讀寫主要是通過傳動手臂上的讀寫磁頭來完成画髓。實(shí)際運(yùn)行時,主軸讓磁盤盤片轉(zhuǎn)動平委,然后傳動手臂可伸展讓讀取頭在盤片上進(jìn)行讀寫操作
影響硬盤性能的因素
影響磁盤的關(guān)鍵因素是磁盤服務(wù)時間奈虾,即磁盤完成一個I/O請求所花費(fèi)的時間,它由尋道時間廉赔、旋轉(zhuǎn)延遲和數(shù)據(jù)傳輸時間三部分構(gòu)成肉微。
尋道時間
Tseek是指將讀寫磁頭移動至正確的磁道上所需要的時間。尋道時間越短蜡塌,I/O操作越快碉纳,目前磁盤的平均尋道時間一般在3-15ms。旋轉(zhuǎn)延遲
Trotation是指盤片旋轉(zhuǎn)將請求數(shù)據(jù)所在的扇區(qū)移動到讀寫磁盤下方所需要的時間馏艾。旋轉(zhuǎn)延遲取決于磁盤轉(zhuǎn)速劳曹,通常用磁盤旋轉(zhuǎn)一周所需時間的1/2表示。比如:7200rpm的磁盤平均旋轉(zhuǎn)延遲大約為60*1000/7200/2 = 4.17ms琅摩,而轉(zhuǎn)速為15000rpm的磁盤其平均旋轉(zhuǎn)延遲為2ms厚者。數(shù)據(jù)傳輸時間
Ttransfer是指完成傳輸所請求的數(shù)據(jù)所需要的時間,它取決于數(shù)據(jù)傳輸率迫吐,其值等于數(shù)據(jù)大小除以數(shù)據(jù)傳輸率。目前IDE/ATA能達(dá)到133MB/s账忘,SATA II可達(dá)到300MB/s的接口數(shù)據(jù)傳輸率志膀,數(shù)據(jù)傳輸時間通常遠(yuǎn)小于前兩部分消耗時間熙宇。簡單計(jì)算時可忽略。
衡量性能的指標(biāo)
機(jī)械硬盤的連續(xù)讀寫性能很好溉浙,但隨機(jī)讀寫性能很差烫止,這主要是因?yàn)榇蓬^移動到正確的磁道上需要時間,隨機(jī)讀寫時戳稽,磁頭需要不停的移動馆蠕,時間都浪費(fèi)在了磁頭尋址上,所以性能不高惊奇。衡量磁盤的重要主要指標(biāo)是IOPS和吞吐量互躬。
- IOPS
IOPS(Input/Output Per Second)即每秒的輸入輸出量(或讀寫次數(shù)),即指每秒內(nèi)系統(tǒng)能處理的I/O請求數(shù)量颂郎。隨機(jī)讀寫頻繁的應(yīng)用吼渡,如小文件存儲等,關(guān)注隨機(jī)讀寫性能乓序,IOPS是關(guān)鍵衡量指標(biāo)寺酪。可以推算出磁盤的IOPS = 1000ms / (Tseek + Trotation + Transfer)替劈,如果忽略數(shù)據(jù)傳輸時間寄雀,理論上可以計(jì)算出隨機(jī)讀寫最大的IOPS。常見磁盤的隨機(jī)讀寫最大IOPS為:
7200rpm的磁盤 IOPS = 76 IOPS
10000rpm的磁盤IOPS = 111 IOPS
15000rpm的磁盤IOPS = 166 IOPS
- 吞吐量
吞吐量(Throughput)陨献,指單位時間內(nèi)可以成功傳輸?shù)臄?shù)據(jù)數(shù)量盒犹。順序讀寫頻繁的應(yīng)用,如視頻點(diǎn)播湿故,關(guān)注連續(xù)讀寫性能阿趁、數(shù)據(jù)吞吐量是關(guān)鍵衡量指標(biāo)。它主要取決于磁盤陣列的架構(gòu)坛猪,通道的大小以及磁盤的個數(shù)脖阵。不同的磁盤陣列存在不同的架構(gòu),但他們都有自己的內(nèi)部帶寬墅茉,一般情況下命黔,內(nèi)部帶寬都設(shè)計(jì)足夠充足,不會存在瓶頸就斤。磁盤陣列與服務(wù)器之間的數(shù)據(jù)通道對吞吐量影響很大悍募,比如一個2Gbps的光纖通道,其所能支撐的最大流量僅為250MB/s洋机。最后坠宴,當(dāng)前面的瓶頸都不再存在時,硬盤越多的情況下吞吐量越大绷旗。
操作系統(tǒng)層的優(yōu)化
雖然15000rpm的磁盤計(jì)算出的理論最大IOPS僅為166喜鼓,但在實(shí)際運(yùn)行環(huán)境中副砍,實(shí)際磁盤的IOPS往往能夠突破200甚至更高。這其實(shí)就是在系統(tǒng)調(diào)用過程中庄岖,操作系統(tǒng)進(jìn)行了一系列的優(yōu)化豁翎。
那么操作系統(tǒng)是如何操作硬盤的呢?類似于網(wǎng)絡(luò)的分層結(jié)構(gòu)隅忿,下圖顯示了Linux系統(tǒng)中對于磁盤的一次讀請求在核心空間中所要經(jīng)歷的層次模型心剥。從圖中看出:對于磁盤的一次讀請求,首先經(jīng)過虛擬文件系統(tǒng)層(VFS Layer)背桐,其次是具體的文件系統(tǒng)層(例如Ext2)优烧,接下來是Cache層(Page Cache Layer)、通用塊層(Generic Block Layer)牢撼、I/O調(diào)度層(I/O Scheduler Layer)匙隔、塊設(shè)備驅(qū)動層(Block Device Driver Layer),最后是物理塊設(shè)備層(Block Device Layer)
虛擬文件系統(tǒng)層(VFS Layer)
VFS(Virtual File System)虛擬文件系統(tǒng)是一種軟件機(jī)制熏版,更確切的說扮演著文件系統(tǒng)管理者的角色纷责,與它相關(guān)的數(shù)據(jù)結(jié)構(gòu)只存在于物理內(nèi)存當(dāng)中。它的作用是:屏蔽下層具體文件系統(tǒng)操作的差異撼短,為上層的操作提供一個統(tǒng)一的接口再膳。正是因?yàn)橛辛诉@個層次,Linux中允許眾多不同的文件系統(tǒng)共存并且對文件的操作可以跨文件系統(tǒng)而執(zhí)行曲横。
VFS中包含著向物理文件系統(tǒng)轉(zhuǎn)換的一系列數(shù)據(jù)結(jié)構(gòu)喂柒,如VFS超級塊、VFS的Inode禾嫉、各種操作函數(shù)的轉(zhuǎn)換入口等灾杰。Linux中VFS依靠四個主要的數(shù)據(jù)結(jié)構(gòu)來描述其結(jié)構(gòu)信息,分別為超級塊熙参、索引結(jié)點(diǎn)艳吠、目錄項(xiàng)和文件對象。
超級塊(Super Block):超級塊對象表示一個文件系統(tǒng)孽椰。它存儲一個已安裝的文件系統(tǒng)的控制信息昭娩,包括文件系統(tǒng)名稱(比如Ext2)、文件系統(tǒng)的大小和狀態(tài)黍匾、塊設(shè)備的引用和元數(shù)據(jù)信息(比如空閑列表等等)栏渺。VFS超級塊存在于內(nèi)存中,它在文件系統(tǒng)安裝時建立锐涯,并且在文件系統(tǒng)卸載時自動刪除磕诊。同時需要注意的是對于每個具體的文件系統(tǒng)來說,也有各自的超級塊,它們存放于磁盤秀仲。
索引結(jié)點(diǎn)(Inode):索引結(jié)點(diǎn)對象存儲了文件的相關(guān)元數(shù)據(jù)信息融痛,例如:文件大小、設(shè)備標(biāo)識符神僵、用戶標(biāo)識符、用戶組標(biāo)識符等等覆劈。Inode分為兩種:一種是VFS的Inode保礼,一種是具體文件系統(tǒng)的Inode。前者在內(nèi)存中责语,后者在磁盤中炮障。所以每次其實(shí)是將磁盤中的Inode調(diào)進(jìn)填充內(nèi)存中的Inode,這樣才是算使用了磁盤文件Inode坤候。當(dāng)創(chuàng)建一個文件的時候胁赢,就給文件分配了一個Inode。一個Inode只對應(yīng)一個實(shí)際文件白筹,一個文件也會只有一個Inode智末。
目錄項(xiàng)(Dentry):引入目錄項(xiàng)對象的概念主要是出于方便查找文件的目的。不同于前面的兩個對象徒河,目錄項(xiàng)對象沒有對應(yīng)的磁盤數(shù)據(jù)結(jié)構(gòu)系馆,只存在于內(nèi)存中。一個路徑的各個組成部分顽照,不管是目錄還是普通的文件由蘑,都是一個目錄項(xiàng)對象。如代兵,在路徑/home/source/test.java中尼酿,目錄 /, home, source和文件 test.java都對應(yīng)一個目錄項(xiàng)對象植影。VFS在查找的時候句惯,根據(jù)一層一層的目錄項(xiàng)找到對應(yīng)的每個目錄項(xiàng)的Inode抢野,那么沿著目錄項(xiàng)進(jìn)行操作就可以找到最終的文件恃轩。
文件對象(File):文件對象描述的是進(jìn)程已經(jīng)打開的文件。因?yàn)橐粋€文件可以被多個進(jìn)程打開,所以一個文件可以存在多個文件對象。一個文件對應(yīng)的文件對象可能不是惟一的,但是其對應(yīng)的索引節(jié)點(diǎn)和目錄項(xiàng)對象肯定是惟一的。
Page Cache層
引入Cache層的目的是為了提高Linux操作系統(tǒng)對磁盤訪問的性能。Cache層在內(nèi)存中緩存了磁盤上的部分?jǐn)?shù)據(jù)。當(dāng)數(shù)據(jù)的請求到達(dá)時,如果在Cache中存在該數(shù)據(jù)且是最新的,則直接將數(shù)據(jù)傳遞給用戶程序,免除了對底層磁盤的操作,提高了性能碗硬。Cache層也正是磁盤IOPS為什么能突破200的主要原因之一翰意。
在Linux的實(shí)現(xiàn)中,文件Cache分為兩個層面,一是Page Cache,另一個Buffer Cache抵赢,每一個Page Cache包含若干Buffer Cache鹏往。Page Cache主要用來作為文件系統(tǒng)上的文件數(shù)據(jù)的緩存來用唐瀑,尤其是針對當(dāng)進(jìn)程對文件有read/write操作的時候奠货。Buffer Cache則主要是設(shè)計(jì)用來在系統(tǒng)對塊設(shè)備進(jìn)行讀寫的時候介褥,對塊進(jìn)行數(shù)據(jù)緩存的系統(tǒng)來使用柔滔。
磁盤Cache有兩大功能:預(yù)讀和回寫嘶朱。預(yù)讀其實(shí)就是利用了局部性原理倘零,具體過程是:對于每個文件的第一個讀請求,系統(tǒng)讀入所請求的頁面并讀入緊隨其后的少數(shù)幾個頁面(通常是三個頁面)戳寸,這時的預(yù)讀稱為同步預(yù)讀袖瞻。對于第二次讀請求,如果所讀頁面不在Cache中订晌,即不在前次預(yù)讀的頁中虏辫,則表明文件訪問不是順序訪問,系統(tǒng)繼續(xù)采用同步預(yù)讀锈拨;如果所讀頁面在Cache中砌庄,則表明前次預(yù)讀命中,操作系統(tǒng)把預(yù)讀頁的大小擴(kuò)大一倍奕枢,此時預(yù)讀過程是異步的娄昆,應(yīng)用程序可以不等預(yù)讀完成即可返回,只要后臺慢慢讀頁面即可缝彬,這時的預(yù)讀稱為異步預(yù)讀萌焰。任何接下來的讀請求都會處于兩種情況之一:第一種情況是所請求的頁面處于預(yù)讀的頁面中,這時繼續(xù)進(jìn)行異步預(yù)讀谷浅;第二種情況是所請求的頁面處于預(yù)讀頁面之外扒俯,這時系統(tǒng)就要進(jìn)行同步預(yù)讀奶卓。
回寫是通過暫時將數(shù)據(jù)存在Cache里,然后統(tǒng)一異步寫到磁盤中撼玄。通過這種異步的數(shù)據(jù)I/O模式解決了程序中的計(jì)算速度和數(shù)據(jù)存儲速度不匹配的鴻溝夺姑,減少了訪問底層存儲介質(zhì)的次數(shù),使存儲系統(tǒng)的性能大大提高掌猛。Linux 2.6.32內(nèi)核之前盏浙,采用pdflush機(jī)制來將臟頁真正寫到磁盤中,什么時候開始回寫呢荔茬?下面兩種情況下废膘,臟頁會被寫回到磁盤:
- 在空閑內(nèi)存低于一個特定的閾值時,內(nèi)核必須將臟頁寫回磁盤慕蔚,以便釋放內(nèi)存丐黄。
- 當(dāng)臟頁在內(nèi)存中駐留超過一定的閾值時,內(nèi)核必須將超時的臟頁寫會磁盤孔飒,以確保臟頁不會無限期地駐留在內(nèi)存中孵稽。
回寫開始后,pdflush會持續(xù)寫數(shù)據(jù)十偶,直到滿足以下兩個條件:
- 已經(jīng)有指定的最小數(shù)目的頁被寫回到磁盤。
- 空閑內(nèi)存頁已經(jīng)回升园细,超過了閾值惦积。
Linux 2.6.32內(nèi)核之后,放棄了原有的pdflush機(jī)制猛频,改成了bdi_writeback機(jī)制狮崩。bdi_writeback機(jī)制主要解決了原有fdflush機(jī)制存在的一個問題:在多磁盤的系統(tǒng)中,pdflush管理了所有磁盤的Cache鹿寻,從而導(dǎo)致一定程度的I/O瓶頸睦柴。bdi_writeback機(jī)制為每個磁盤都創(chuàng)建了一個線程,專門負(fù)責(zé)這個磁盤的Page Cache的刷新工作毡熏,從而實(shí)現(xiàn)了每個磁盤的數(shù)據(jù)刷新在線程級的分離坦敌,提高了I/O性能。
回寫機(jī)制存在的問題是回寫不及時引發(fā)數(shù)據(jù)丟失(可由sync|fsync解決)痢法,回寫期間讀I/O性能很差狱窘。
通用塊層
通用塊層的主要工作是:接收上層發(fā)出的磁盤請求,并最終發(fā)出I/O請求财搁。該層隱藏了底層硬件塊設(shè)備的特性蘸炸,為塊設(shè)備提供了一個通用的抽象視圖。
對于VFS和具體的文件系統(tǒng)來說尖奔,塊(Block)是基本的數(shù)據(jù)傳輸單元搭儒,當(dāng)內(nèi)核訪問文件的數(shù)據(jù)時穷当,它首先從磁盤上讀取一個塊。但是對于磁盤來說淹禾,扇區(qū)是最小的可尋址單元馁菜,塊設(shè)備無法對比它還小的單元進(jìn)行尋址和操作。由于扇區(qū)是磁盤的最小可尋址單元稀拐,所以塊不能比扇區(qū)還小火邓,只能整數(shù)倍于扇區(qū)大小,即一個塊對應(yīng)磁盤上的一個或多個扇區(qū)德撬。一般來說铲咨,塊大小是2的整數(shù)倍,而且由于Page Cache層的最小單元是頁(Page)蜓洪,所以塊大小不能超過一頁的長度纤勒。
大多情況下,數(shù)據(jù)的傳輸通過DMA方式隆檀。舊的磁盤控制器摇天,僅僅支持簡單的DMA操作:每次數(shù)據(jù)傳輸,只能傳輸磁盤上相鄰的扇區(qū)恐仑,即數(shù)據(jù)在內(nèi)存中也是連續(xù)的泉坐。這是因?yàn)槿绻麄鬏敺沁B續(xù)的扇區(qū),會導(dǎo)致磁盤花費(fèi)更多的時間在尋址操作上裳仆。而現(xiàn)在的磁盤控制器支持“分散/聚合”DMA操作腕让,這種模式下,數(shù)據(jù)傳輸可以在多個非連續(xù)的內(nèi)存區(qū)域中進(jìn)行歧斟。為了利用“分散/聚合”DMA操作纯丸,塊設(shè)備驅(qū)動必須能處理被稱為段(segments)的數(shù)據(jù)單元。一個段就是一個內(nèi)存頁面或一個頁面的部分静袖,它包含磁盤上相鄰扇區(qū)的數(shù)據(jù)觉鼻。
通用塊層是粘合所有上層和底層的部分,一個頁的磁盤數(shù)據(jù)布局如下圖所示:
I/O調(diào)度層
I/O調(diào)度層的功能是管理塊設(shè)備的請求隊(duì)列队橙。即接收通用塊層發(fā)出的I/O請求坠陈,緩存請求并試圖合并相鄰的請求。并根據(jù)設(shè)置好的調(diào)度算法捐康,回調(diào)驅(qū)動層提供的請求處理函數(shù)畅姊,以處理具體的I/O請求。
如果簡單地以內(nèi)核產(chǎn)生請求的次序直接將請求發(fā)給塊設(shè)備的話吹由,那么塊設(shè)備性能肯定讓人難以接受若未,因?yàn)榇疟P尋址是整個計(jì)算機(jī)中最慢的操作之一。為了優(yōu)化尋址操作倾鲫,內(nèi)核不會一旦接收到I/O請求后粗合,就按照請求的次序發(fā)起塊I/O請求萍嬉。為此Linux實(shí)現(xiàn)了幾種I/O調(diào)度算法,算法基本思想就是通過合并和排序I/O請求隊(duì)列中的請求隙疚,以此大大降低所需的磁盤尋道時間壤追,從而提高整體I/O性能。
常見的I/O調(diào)度算法包括Noop調(diào)度算法(No Operation)供屉、CFQ(完全公正排隊(duì)I/O調(diào)度算法)行冰、DeadLine(截止時間調(diào)度算法)、AS預(yù)測調(diào)度算法等伶丐。
Noop算法:最簡單的I/O調(diào)度算法悼做。該算法僅適當(dāng)合并用戶請求,并不排序請求哗魂。新的請求通常被插在調(diào)度隊(duì)列的開頭或末尾肛走,下一個要處理的請求總是隊(duì)列中的第一個請求。這種算法是為不需要尋道的塊設(shè)備設(shè)計(jì)的录别,如SSD朽色。因?yàn)槠渌齻€算法的優(yōu)化是基于縮短尋道時間的,而SSD硬盤沒有所謂的尋道時間且I/O響應(yīng)時間非常短组题。
CFQ算法:算法的主要目標(biāo)是在觸發(fā)I/O請求的所有進(jìn)程中確保磁盤I/O帶寬的公平分配葫男。算法使用許多個排序隊(duì)列,存放了不同進(jìn)程發(fā)出的請求崔列。通過散列將同一個進(jìn)程發(fā)出的請求插入同一個隊(duì)列中腾誉。采用輪詢方式掃描隊(duì)列,從第一個非空隊(duì)列開始峻呕,依次調(diào)度不同隊(duì)列中特定個數(shù)(公平)的請求,然后將這些請求移動到調(diào)度隊(duì)列的末尾趣效。
Deadline算法:算法引入了兩個排隊(duì)隊(duì)列分別包含讀請求和寫請求瘦癌,兩個最后期限隊(duì)列包含相同的讀和寫請求。本質(zhì)就是一個超時定時器跷敬,當(dāng)請求被傳給電梯算法時開始計(jì)時讯私。一旦最后期限隊(duì)列中的超時時間已到,就想請求移至調(diào)度隊(duì)列末尾西傀。Deadline算法避免了電梯調(diào)度策略(為了減少尋道時間斤寇,會優(yōu)先處理與上一個請求相近的請求)帶來的對某個請求忽略很長一段時間的可能。
AS算法:AS算法本質(zhì)上依據(jù)局部性原理拥褂,預(yù)測進(jìn)程發(fā)出的讀請求與剛被調(diào)度的請求在磁盤上可能是“近鄰”娘锁。算法統(tǒng)計(jì)每個進(jìn)程I/O操作信息,當(dāng)剛剛調(diào)度了由某個進(jìn)程的一個讀請求之后饺鹃,算法馬上檢查排序隊(duì)列中的下一個請求是否來自同一個進(jìn)程莫秆。如果是间雀,立即調(diào)度下一個請求。否則镊屎,查看關(guān)于該進(jìn)程的統(tǒng)計(jì)信息惹挟,如果確定進(jìn)程p可能很快發(fā)出另一個讀請求,那么就延遲一小段時間缝驳。
前文中計(jì)算出的IOPS是理論上的隨機(jī)讀寫的最大IOPS连锯,在隨機(jī)讀寫中,每次I/O操作的尋址和旋轉(zhuǎn)延時都不能忽略不計(jì)用狱,有了這兩個時間的存在也就限制了IOPS的大小≡瞬溃現(xiàn)在如果我們考慮在讀取一個很大的存儲連續(xù)分布在磁盤的文件,因?yàn)槲募拇鎯Φ姆植际沁B續(xù)的齿拂,磁頭在完成一個讀I/O操作之后驳规,不需要重新尋址,也不需要旋轉(zhuǎn)延時署海,在這種情況下我們能到一個很大的IOPS值吗购。這時由于不再考慮尋址和旋轉(zhuǎn)延時,則性能瓶頸僅是數(shù)據(jù)傳輸時延砸狞,假設(shè)數(shù)據(jù)傳輸時延為0.4ms捻勉,那么IOPS=1000 / 0.4 = 2500 IOPS。
在許多的開源框架如Kafka刀森、HBase中踱启,都通過追加寫的方式來盡可能的將隨機(jī)I/O轉(zhuǎn)換為順序I/O,以此來降低尋址時間和旋轉(zhuǎn)延時研底,從而最大限度的提高IOPS埠偿。
塊設(shè)備驅(qū)動層
驅(qū)動層中的驅(qū)動程序?qū)?yīng)具體的物理塊設(shè)備。它從上層中取出I/O請求榜晦,并根據(jù)該I/O請求中指定的信息冠蒋,通過向具體塊設(shè)備的設(shè)備控制器發(fā)送命令的方式,來操縱設(shè)備傳輸數(shù)據(jù)乾胶。這里不再贅述抖剿。
基于磁盤I/O特性設(shè)計(jì)的技巧
在上一節(jié)中我們了解了Linux系統(tǒng)中請求到達(dá)磁盤的一次完整過程,期間Linux通過Cache以及排序合并I/O請求來提高系統(tǒng)的性能识窿。其本質(zhì)就是由于磁盤隨機(jī)讀寫慢斩郎、順序讀寫快。本節(jié)針對常見開源系統(tǒng)闡述一些基于磁盤I/O特性的設(shè)計(jì)技巧喻频。
采用追加寫
在進(jìn)行系統(tǒng)設(shè)計(jì)時缩宜,良好的讀性能和寫性能往往不可兼得。在許多常見的開源系統(tǒng)中都是優(yōu)先在保證寫性能的前提下來優(yōu)化讀性能甥温。那么如何設(shè)計(jì)能讓一個系統(tǒng)擁有良好的寫性能呢脓恕?一個好的辦法就是采用追加寫膜宋,每次將數(shù)據(jù)添加到文件。由于完全是順序的炼幔,所以可以具有非常好的寫操作性能秋茫。但是這種方式也存在一些缺點(diǎn):從文件中讀一些數(shù)據(jù)時將會需要更多的時間:需要倒序掃描,直到找到所需要的內(nèi)容乃秀。當(dāng)然在一些簡單的場景下也能夠保證讀操作的性能:
-
數(shù)據(jù)是被整體訪問肛著,比如HDFS
- HDFS建立在一次寫多次讀的模型之上。在HDFS中就是采用了追加寫并且設(shè)計(jì)為高數(shù)據(jù)吞吐量跺讯;高吞吐量必然以高延遲為代價枢贿,所以HDFS并不適用于對數(shù)據(jù)訪問要求低延遲的場景;由于采用是的追加寫刀脏,也并不適用于任意修改文件的場景局荚。HDFS設(shè)計(jì)為流式訪問大文件,使用大數(shù)據(jù)塊并且采用流式數(shù)據(jù)訪問來保證數(shù)據(jù)被整體訪問愈污,同時最小化硬盤的尋址開銷耀态,只需要一次尋址即可,這時尋址時間相比于傳輸時延可忽略暂雹,從而也擁有良好的讀性能首装。HDFS不適合存儲小文件,原因之一是由于NameNode內(nèi)存不足問題杭跪,還有就是因?yàn)樵L問大量小文件需要執(zhí)行大量的尋址操作仙逻,并且需要不斷的從一個datanode跳到另一個datanode,這樣會大大降低數(shù)據(jù)訪問性能涧尿。
-
知道文件明確的偏移量系奉,比如Kafka
- 在Kafka中,采用消息追加的方式來寫入每個消息姑廉,每個消息讀寫時都會利用Page Cache的預(yù)讀和后寫特性缺亮,同時partition中都使用順序讀寫,以此來提高I/O性能庄蹋。雖然Kafka能夠根據(jù)偏移量查找到具體的某個消息,但是查找過程是順序查找迷雪,因此如果數(shù)據(jù)很大的話限书,查找效率就很低。所以Kafka中采用了分段和索引的方式來解決查找效率問題章咧。Kafka把一個patition大文件又分成了多個小文件段倦西,每個小文件段以偏移量命名,通過多個小文件段赁严,不僅可以使用二分搜索法很快定位消息扰柠,同時也容易定期清除或刪除已經(jīng)消費(fèi)完的文件粉铐,減少磁盤占用。為了進(jìn)一步提高查找效率卤档,Kafka為每個分段后的數(shù)據(jù)建立了索引文件蝙泼,并通過索引文件稀疏存儲來降低元數(shù)據(jù)占用大小。一個段中數(shù)據(jù)對應(yīng)結(jié)構(gòu)如下圖所示:
在面對更復(fù)雜的讀場景(比如按key)時劝枣,如何來保證讀操作的性能呢汤踏?簡單的方式是像Kafka那樣,將文件數(shù)據(jù)有序保存舔腾,使用二分查找來優(yōu)化效率溪胶;或者通過建索引的方式來進(jìn)行優(yōu)化;也可以采用hash的方式將數(shù)據(jù)分割為不同的桶稳诚。以上的方法都能增加讀操作的性能哗脖,但是由于在數(shù)據(jù)上強(qiáng)加了數(shù)據(jù)結(jié)構(gòu),又會降低寫操作的性能扳还。比如如果采用索引的方式來優(yōu)化讀操作才避,那么在更新索引時就需要更新B-tree中的特定部分,這時候的寫操作就是隨機(jī)寫普办。那么有沒有一種辦法在保證寫性能不損失的同時也提供較好的讀性能呢工扎?一個好的選擇就是使用LSM-tree。LSM-tree與B-tree相比衔蹲,LSM-tree犧牲了部分讀操作肢娘,以此大幅提高寫性能。
- 日志結(jié)構(gòu)的合并樹LSM(The Log-Structured Merge-Tree)是HBase舆驶,LevelDB等NoSQL數(shù)據(jù)庫的存儲引擎橱健。Log-Structured的思想是將整個磁盤看做一個日志,在日志中存放永久性數(shù)據(jù)及其索引沙廉,每次都添加到日志的末尾拘荡。并且通過將很多小文件的存取轉(zhuǎn)換為連續(xù)的大批量傳輸,使得對于文件系統(tǒng)的大多數(shù)存取都是順序的撬陵,從而提高磁盤I/O珊皿。LSM-tree就是這樣一種采用追加寫、數(shù)據(jù)有序以及將隨機(jī)I/O轉(zhuǎn)換為順序I/O的延遲更新巨税,批量寫入硬盤的數(shù)據(jù)結(jié)構(gòu)蟋定。LSM-tree將數(shù)據(jù)的修改增量先保存在內(nèi)存中,達(dá)到指定的大小限制后再將這些修改操作批量寫入磁盤草添。因此比較舊的文件不會被更新驶兜,重復(fù)的紀(jì)錄只會通過創(chuàng)建新的紀(jì)錄來覆蓋,這也就產(chǎn)生了一些冗余的數(shù)據(jù)。所以系統(tǒng)會周期性的合并一些數(shù)據(jù)抄淑,移除重復(fù)的更新或者刪除紀(jì)錄屠凶,同時也會刪除上述的冗余。在進(jìn)行讀操作時肆资,如果內(nèi)存中沒有找到相應(yīng)的key矗愧,那么就是倒序從一個個磁盤文件中查找。如果文件越來越多那么讀性能就會越來越低迅耘,目前的解決方案是采用頁緩存來減少查詢次數(shù)贱枣,周期合并文件也有助于提高讀性能。在文件越來越多時颤专,可通過布隆過濾器來避免大量的讀文件操作纽哥。LSM-tree犧牲了部分讀性能,以此來換取寫入的最大化性能栖秕,特別適用于讀需求低春塌,會產(chǎn)生大量插入操作的應(yīng)用環(huán)境。
文件合并和元數(shù)據(jù)優(yōu)化
目前的大多數(shù)文件系統(tǒng)簇捍,如XFS/Ext4只壳、GFS、HDFS暑塑,在元數(shù)據(jù)管理吼句、緩存管理等實(shí)現(xiàn)策略上都側(cè)重大文件。上述基于磁盤I/O特性設(shè)計(jì)的系統(tǒng)都有一個共性特點(diǎn)就是都運(yùn)行在這些文件系統(tǒng)之上事格。這些文件系統(tǒng)在面臨海量時在性能和存儲效率方面都大幅降低惕艳,本節(jié)來探討下海量小文件下的系統(tǒng)設(shè)計(jì)。
常見文件系統(tǒng)在海量小文件應(yīng)用下性能表現(xiàn)不佳的根本原因是磁盤最適合順序的大文件I/O讀寫模式驹愚,而非常不適合隨機(jī)的小文件I/O讀寫模式远搪。主要原因體現(xiàn)在元數(shù)據(jù)管理低效和數(shù)據(jù)布局低效:
元數(shù)據(jù)管理低效:由于小文件數(shù)據(jù)內(nèi)容較少,因此元數(shù)據(jù)的訪問性能對小文件訪問性能影響巨大逢捺。Ext2文件系統(tǒng)中Inode和Data Block分別保存在不同的物理位置上谁鳍,一次讀操作需要至少經(jīng)過兩次的獨(dú)立訪問。在海量小文件應(yīng)用下劫瞳,Inode的頻繁訪問倘潜,使得原本的并發(fā)訪問轉(zhuǎn)變?yōu)榱撕A康碾S機(jī)訪問,大大降低了性能志于。另外涮因,大量的小文件會快速耗盡Inode資源,導(dǎo)致磁盤盡管有大量Data Block剩余也無法存儲文件恨憎,會浪費(fèi)磁盤空間蕊退。
數(shù)據(jù)布局低效:Ext2在Inode中使用多級指針來索引數(shù)據(jù)塊。對于大文件憔恳,數(shù)據(jù)塊的分配會盡量連續(xù)瓤荔,這樣會具有比較好的空間局部性。但是對于小文件钥组,數(shù)據(jù)塊可能零散分布在磁盤上的不同位置输硝,并且會造成大量的磁盤碎片,不僅造成訪問性能下降程梦,還大量浪費(fèi)了磁盤空間点把。數(shù)據(jù)塊一般為1KB、2KB或4KB屿附,對于小于4KB的小文件郎逃,Inode與數(shù)據(jù)的分開存儲破壞了空間局部性,同時也造成了大量的隨機(jī)I/O挺份。
對于海量小文件應(yīng)用褒翰,常見的I/O流程復(fù)雜也是造成磁盤性能不佳的原因。對于小文件匀泊,磁盤的讀寫所占用的時間較少优训,而用于文件的open()操作占用了絕大部分系統(tǒng)時間,導(dǎo)致磁盤有效服務(wù)時間非常低各聘,磁盤性能低下揣非。針對于問題的根源,優(yōu)化的思路大體上分為:
- 針對數(shù)據(jù)布局低效躲因,采用小文件合并策略早敬,將小文件合并為大文件。
- 針對元數(shù)據(jù)管理低效毛仪,優(yōu)化元數(shù)據(jù)的存儲和管理搁嗓。針對這兩種優(yōu)化方式,業(yè)內(nèi)也出現(xiàn)了許多優(yōu)秀的開源軟件箱靴。
小文件合并
小文件合并為大文件后腺逛,首先減少了大量元數(shù)據(jù),提高了元數(shù)據(jù)的檢索和查詢效率衡怀,降低了文件讀寫的I/O操作延時棍矛。其次將可能連續(xù)訪問的小文件一同合并存儲,增加了文件之間的局部性抛杨,將原本小文件間的隨機(jī)訪問變?yōu)榱隧樞蛟L問够委,大大提高了性能。同時怖现,合并存儲能夠有效的減少小文件存儲時所產(chǎn)生的磁盤碎片問題茁帽,提高了磁盤的利用率玉罐。最后,合并之后小文件的訪問流程也有了很大的變化潘拨,由原來許多的open操作轉(zhuǎn)變?yōu)榱藄eek操作,定位到大文件具體的位置即可铁追。如何尋址這個大文件中的小文件呢琅束?其實(shí)就是利用一個旁路數(shù)據(jù)庫來記錄每個小文件在這個大文件中的偏移量和長度等信息扭屁。其實(shí)小文件合并的策略本質(zhì)上就是通過分層的思想來存儲元數(shù)據(jù)。中控節(jié)點(diǎn)存儲一級元數(shù)據(jù)涩禀,也就是大文件與底層塊的對應(yīng)關(guān)系;數(shù)據(jù)節(jié)點(diǎn)存放二級元數(shù)據(jù)幔欧,也就是最終的用戶文件在這些一級大塊中的存儲位置對應(yīng)關(guān)系,經(jīng)過兩級尋址來讀寫數(shù)據(jù)丽声。
- 淘寶的TFS就采用了小文件合并存儲的策略礁蔗。TFS中默認(rèn)Block大小為64M,每個塊中會存儲許多不同的小文件浴井,但是這個塊只占用一個Inode霉撵。假設(shè)一個Block為64M徒坡,數(shù)量級為1PB。那么NameServer上會有 1 *1024 *1024 * 1024 / 64 = 16.7M個Block喇完。假設(shè)每個Block的元數(shù)據(jù)大小為0.1K锦溪,則占用內(nèi)存不到2G。在TFS中防楷,文件名中包含了Block ID和File ID则涯,通過Block ID定位到具體的DataServer上冲簿,然后DataServer會根據(jù)本地記錄的信息來得到File ID所在Block的偏移量民假,從而讀取到正確的文件內(nèi)容龙优。TFS一次讀過程如下圖所示:
元數(shù)據(jù)管理優(yōu)化
一般來說元數(shù)據(jù)信息包括名稱彤断、文件大小易迹、設(shè)備標(biāo)識符既棺、用戶標(biāo)識符、用戶組標(biāo)識符等等,在小文件系統(tǒng)中可以對元數(shù)據(jù)信息進(jìn)行精簡炫乓,僅保存足夠的信息即可闸衫。元數(shù)據(jù)精簡可以減少元數(shù)據(jù)通信延時,同時相同容量的Cache能存儲更多的元數(shù)據(jù)弟翘,從而提高元數(shù)據(jù)使用效率稀余。另外可以在文件名中就包含元數(shù)據(jù)信息趋翻,從而減少一個元數(shù)據(jù)的查詢操作。最后針對特別小的一些文件掸掏,可以采取元數(shù)據(jù)和數(shù)據(jù)并存的策略宙帝,將數(shù)據(jù)直接存儲在元數(shù)據(jù)之中,通過減少一次尋址操作從而大大提高性能步脓。
- TFS中文件命名就隱含了位置信息等部分元數(shù)據(jù)愿待,從而減少了一個元數(shù)據(jù)的查詢操作仍侥。在Rerserfs中,對于小于1KB的小文件患蹂,Rerserfs可以將數(shù)據(jù)直接存儲在Inode中。