龔 恒,李小勇
(上海交通大學 網絡空間安全學院,上海 200240)
傳統意義上,文件系統是操作系統內核的一部分。隨著分布式存儲系統[1]的出現和大數據分析技術[2]需求的增大,面對海量的數據文件,用戶實現數據訪問的步驟也越來越繁瑣,開發面向用戶場景的用戶態文件系統[3]勢在必行。
本文底層使用分布式對象存儲系統,結合fuse[4-5]技術和分布式數據庫MongoDB[6],設計基于分布式存儲的用戶態文件系統bfs-fuse,以文件目錄形式對分布式對象存儲系統中的對象進行管理和訪問,從而為用戶提供高效便捷的數據訪問管理策略。
底層數據使用分布式存儲系統。分布式存儲將數據對象分散到多個存儲服務器上,并將這些分散的數據資源通過統籌管理合并為一個虛擬統一的存儲系統。
典型的分布式存儲架構,如圖1所示。

圖1 分布式存儲架構
它通常由3個部分組成——管理節點(Manager Node)、數據節點(Data Node)和應用客戶端(Client)。其中,管理節點負責管理集群元數據、協調集群服務、響應客戶端發來的數據請求等。在整個集群架構中,管理節點是最核心的部分,通常采用主從、多活等方式增加整個架構的穩定性和安全性。數據節點主要負責存儲用戶數據,保證用戶數據的可用性和完整性;應用客戶端負責發送數據的讀寫請求,緩存元數據和用戶數據。
20世紀末,麻省理工學院Karger等人提出一致性哈希算法,解決了分布式結構中動態增加和刪除帶來的雪崩問題以及數據分布不均衡問題,在分布式系統中得到了廣泛應用。
本文使用一致性哈希算法作為分布式存儲數據節點的數據分布算法。集群中同一個存儲池中的所有數據對象,經過一致性哈希運算后被分散存儲到該存儲池中的所有存儲設備上。為增強數據分布的均衡性,在數據和存儲節點之間再引入一層虛擬節點(Cube)層。虛擬節點可以理解為存儲節點在哈希空間中的復制品,其總數遠大于存儲節點的數量,且按照不同存儲節點在容量和性能上的差異以及負載均衡的要求進行分布。圖2展示的是系統在查找對象號object_id為obj_1的對象的查找映射過程。

圖2 數據分布查找映射
數據存儲采用多副本方式來提高數據的高可用性,各副本將被盡量存儲到不同的數據服務器。初始狀態下,容器中所有對象默認以雙副本存儲,也可在創建每個容器時對容器的副本數進行單獨配置。對于相對重要的文件,通常以三副本保存。系統中每個對象對應的文件擴展屬性中保存了數據版本號和隨機序列數,兩者結合進行版本管理和一致性保證。
在讀取數據流程中,客戶端首先從三個副本所在的數據服務器拉取版本號,選擇版本號最新的副本作為正確數據向上層應用返回,并將版本號錯誤的副本信息報告給對應的數據服務器,以便在之后進行數據修復。在寫入數據流程中,客戶端將優先寫主副本,然后由主副本所在數據服務器采用扇出寫的方式向另外兩臺數據服務器寫從副本。為了平衡性能與一致性,底層向上層應用保證最終一致性,即只有主副本落盤且確認另外兩個從副本均已成功推送至對應數據服務器后向客戶端返回數據寫入成功。
元數據的管理是分布式文件系統的核心,也是分布式存儲系統架構和保障的重中之重。
為了提高文件元數據的一致性和可靠性,支持用戶文件系統目錄操作,本文使用MongoDB數據庫對分布式存儲系統存儲的文件元數據進行集成管理。文件元數據最基本的作用在于數據對象的定位,客戶端只有先訪問存儲元數據的管理節點,得到數據對象的inode號以及相關的訪問權限,才能進行下一步的數據讀寫訪問。元數據服務模塊架構如圖3所示。

圖3 元數據服務模塊架構
為了提高系統的訪問效率,將文件系統中所有目錄和文件用一張統一的表(fs_object)來管理,即用戶空間里的所有文件和目錄都被映射為存儲系統里面的一個個對象,通過關鍵字(is_dir)是否為0來區分文件和目錄。每個對象的元數據信息包括對象ID、對象名、父節點ID、是否是目錄、對象大小、創建時間、修改時間以及父inode號等。在訪問請求中,客戶端只需要訪問一張表就可以獲得文件對象的所有元數據信息,從而減少不必要的開銷,提高元數據的管理效率。
分布式存儲系統通常使用Linux系統作為硬件操作系統,但Linux中文件系統中文件系統都在內核態實現,文件系統功能的增加和修改在內核中需要做很大的工作量,且調試起來非常復雜,而fuse的出現改善了這一情況。
fuse(用戶態文件系統)是一個在用戶空間實現的文件系統框架,通過fuse內核模塊給予支持。基于fuse提供的接口,本文設計實現了支持對象與文件映射和文件操作的文件訪問系統bfs-fuse。
在bfs-fuse中,由內核模塊創建塊設備/dev/fuse,作用是溝通用戶空間進程的fuse進程和系統內核。來自/dev/fuse的所有請求都是通過file requests請求隊列發送到內核,bfs-fuse從/dev/fuse中讀取請求并進行處理。
當用戶態應用程序(如ls、cp、mv、rm等)發起具體請求時,應用程序將文件系統操作請求發送給內核,內核讀取來自該設備的請求。內核中,每一個具體的請求都包含具體文件的inode,內核需要解析文件路徑中每一層路徑的inode號進行拼接,得到文件具體的inode號,再根據文件的inode號從MongoDB數據庫中得到文件映射在存儲系統中對象的object_id,進而通過一致性哈希運算得到對象在分布式儲存中的location地址,最后從分布式系統中取出數據對象,返回給用戶空間的應用程序。它的內核調用流程如圖4所示。
在原有的分布式集群中增加fuse組件和元數據信息的管理,必然會降低文件系統增刪查改對象的效率。為了減少系統性能的損失,在設計bfs-fuse時,本文針對分布式存儲的特性,對fuse掛載組件的部分參數配置進行了如下改進。
(1)提高頁面空間大小。在fuse中,默認請求被分配的page大小是4 kB,但實際中存儲的文件大小通常遠大于4 kB,使得文件寫入時上下文切換頻率增加,降低文件讀寫效率,故bfs-fuse中將頁面空間大小修改為128 kB。
(2)緩存文件的元數據信息。對于只讀和查詢的文件,對文件的inode和dentry結構進行緩存。
(3)數據直接I/O讀寫。對于數據需要直接落盤的應用,允許應用程序跳過緩存,直接將數據寫入存儲設備中。
本文的測試方案通過海量數量小文件讀寫測試和大文件讀寫測試兩個角度模擬實際應用中的I/O需求。通過海量小文件的讀寫,測試系統的性能瓶頸;通過大文件的讀寫,測試系統的吞吐量性能。部署3節點分布式存儲集群作為測試環境,所有硬件服務器均為DELL服務器,內存32 GB,16核CPU,千兆以太網。測試腳本通過調用linux讀寫接口,在磁盤中創建和讀寫文件,并返回讀寫時間和帶寬。

圖4 bfs-fuse內核調用流程
在小文件測試中,分別使用1~1 024個線程。每個線程分別在掛載目錄寫入、讀取100個16 kB大小的文件,用來模擬現實網絡中常見的小文件讀寫,測試結果如圖5所示。

圖5 小文件讀寫測試
從圖5可以看出,由于使用分布式存儲,小文件讀寫的性能隨著線程數的增加而增加;當線程并發數超過256時,讀寫性能趨于穩定,分別是35 MB/s和80 MB/s左右。它的帶寬低于千兆以太網理論帶寬的峰值,這是因為對于海量的小文件,讀寫過程中產生的額外開銷不可忽視,如MongoDB元數據讀寫、文件查詢和定位等。從讀寫整體性能對比來看,相同線程數寫入的帶寬速率要遠高于讀取速率,這是因為小文件批量寫入時采取的是順序寫的方式,但讀取時因為文件大小比較小、數量多,其讀取方式相當于隨機讀取,所以讀取帶寬速率會小于寫入的帶寬速率。
在大文件測試中,分別使用1~1 024個線程。每個線程分別在掛載目錄寫入、讀取4個1 GB大小的文件,用來模擬現實網絡中常見的大文件讀寫,測試結果如圖6所示。其中,隨著線程并發數的增加,讀寫帶寬隨之增加,最終趨近于各自的峰值140 MB/s和120 MB/s。這與千兆以太網理論帶寬相近。

圖6 大文件讀寫測試
從整體來看,系統在吞吐量性能方面有著不錯的表現,但在小文件讀取性能方面有一定的提升空間,因為文件系統通過MongDB訪問元數據帶來的性能開銷在小文件讀寫中無法忽視,建議對MongoDB服務器配置SSD固態磁盤存儲元數據,同時開啟MongoDB緩存提高元數據訪問速率,進而提高海量小文件讀取時的性能。
本文的創新點是在分布式存儲系統的基礎上,結合MongoDB,使用fuse框架設計,實現了面向海量數據的用戶態分布式文件系統,既具有很高的靈活性和可擴展性,又使得用戶對分布式存儲系統的訪問更加便捷,管理更加方便。經過小文件讀寫和大文件吞吐量測試可以發現,本文設計的文件系統在吞吐量性能和小文件創建方面有著優良的性能表現,但在小文件讀取性能方面有進一步的提高空間。