天天看點

小檔案合并存儲問題

LOSF(lots of small files)問題是很多網際網路企業都會遇到的, 文本、圖檔、音樂是典型的小檔案應用場景,比如58同城、淘寶網、蝦米網、汽車之家等網站都是有海量小檔案存儲需求的。

小檔案存儲問題集中表現在如下幾個方面:

1. 小檔案太多,單機無法存儲 2. 小檔案的存取性能 3. 小檔案的高效備份與恢複      

對于問題1,主要是借助分布式技術來解決,單機存儲不了,就将資料分散存儲到多台機器,并通過在軟體層做封裝提供統一的存儲接口,使得存儲服務的使用者不需要關心資料是如何存儲、存儲在哪裡的。

對于問題2,以磁盤為存儲媒體的單機檔案系統(如ext4、xfs等),檔案都是以目錄樹結構來組織的,當檔案數很多時,目錄内的檔案數會很多、目錄層次也會變深,使得路徑查找(sys_open)是非常影響性能的,一次路徑名查找可能需要多次磁盤IO。

對于問題3,小檔案的備份與恢複實際上還是LOSF問題,因為小檔案通路存在性能問題,那麼其備份與恢複肯定也是效率極低的,通常問題1、2解決了,這個問題也就迎刃而解。

TFS(Taobao File System)是淘寶解決海量小檔案存儲自主研發的分布式檔案系統,通過将資料分布到多個存儲節點來解決問題1、通過将多個小檔案打包存儲到大檔案(block)以及扁平化的目錄結構來解決問題2、通過block多副本以及按block複制的方式來解決問題3。      

本文重點探讨多個小檔案存儲到大檔案的問題,除TFS外,HDFS、FastDFS等針對小檔案也有類似的解決方案。把小檔案存儲到大檔案,要解決如何存、如何取得問題。目前比較典型的解決方案是,存儲時将小檔案追加到block的尾部,并為小檔案建立索引用于快速定位;在通路時,先根據索引來擷取檔案在block内的offset和size,然後從block讀取檔案資料;關鍵的問題是索引該如何存儲?

為友善說明問題,約定用于存儲多個小檔案的大檔案稱作一個block,通常64MB大小,block内部存儲的每個檔案由一個fileid辨別。      

方案一

索引檔案不持久化存儲,在記憶體裡組織為hash表(或順序表,如果能接受二分查找的性能);存儲時将檔案追加到block尾部後,将檔案在block内部的offset以及檔案size資訊,插入到hash表中;通路該檔案時,先根據檔案的id在hash表中定位檔案的offset和size,然後在block對應位置讀取檔案資料,由于hash表是全記憶體化的,通路檔案隻需一次IO。

小檔案合并存儲問題

這種方案的優點在于,每次存儲檔案時,隻需要一次IO操作,不會出現索引與block實際檔案資料不一緻大情況;缺點在于,索引隻存在于記憶體,當服務重新開機時,index資訊需要根據block的資料來重建,,這就要求檔案在block中存儲時,必須存儲一些額外的頭資訊,比如magicnum,使得block的檔案具有自描述能力,在每次啟動時,通過掃描block資料來生成index。

以2T磁盤、64MB block為例,磁盤上會有約30000個block;假設掃描一個block需要1s,那麼啟動時間約為500min * 0.8(磁盤使用率80%)= 400min,顯然,每次啟動時掃描block來生成index的開銷是不可接受的。

方案二

在方案一的基礎上,每個block對應一個index檔案,寫檔案時先将檔案資料追加到block的尾部,然後将檔案的index插入到記憶體hash表,最後将檔案的index追加到index檔案;在存儲服務重新開機時,每個block根據其index檔案來快速建立記憶體hash表。

小檔案合并存儲問題

上述方案解決了索引重建的問題,但其每次寫檔案需要兩次IO,寫檔案的延時就高了。為了降低寫的延時,facebook在haystack裡使用了一種折中的方案。檔案寫入時,往block追加檔案資料時同步刷盤,然後将記錄插入到記憶體hash表,接下來追加index記錄時,發送完追加請求就認為寫檔案成功,即index并不立即刷盤,這樣寫的延時縮短到一次IO。

檔案index異步追加,可能導緻一個問題,檔案在block裡存在,但在index檔案裡沒有這個檔案的記錄,在根據index檔案重建記憶體hash表時,hash表就是不完整的,導緻部分檔案通路不到。haystack通過在重建時,從block尾部開始掃描,找出所有可能缺失index的檔案,并生成index追加到index檔案。(因為檔案在block和index裡順序相同,缺失index的檔案一定是在block尾部的一批)

方案三

方案二中index的資料實際上是存在兩份的,一份是記憶體hash表裡的,一份是index檔案裡的,因為linux的頁緩存機制,檔案裡的index也是可能cache在記憶體裡,是以方案二對記憶體的利用不是最優的,可以考慮将index檔案和index記憶體hash表合二為一,将index檔案本身以hash表的方式組織,直接使用mmap将index檔案映射到記憶體。

小檔案合并存儲問題

通過将index檔案和記憶體hash表合二為一,操作記憶體index即為操作index檔案,管理index會友善不少。為了解決hash沖突問題,每個index條目需要額外增加一個next字段用于連接配接沖突鍊。

這種方案還存在hash擴充的問題,當block記憶體儲的小檔案數量很多時,按照預估的hash桶數(預估值通常不會太大,太大會有很多空間浪費),可能會導緻沖突鍊很長,這時要提高hash查找的效率就必須擴充桶的數量,如果使用方案一,擴充隻會導緻額外的記憶體拷貝,而在這個方案裡,則會導緻整個index檔案重寫,會産生IO操作。