摘要:宋體是一款自主研發(fā)的分布式文件存儲產(chǎn)品,此前已推出容量型版本。宋體性能的提升不僅僅是因為存儲介質的升級,更有架構層面的改進,本文將從協(xié)議索引存儲設計等幾方面來詳細介紹性能型升級改造的技術細節(jié)。
UFS (UCloud File System) 是一款 UCloud 自主研發(fā)的分布式文件存儲產(chǎn)品,此前已推出容量型 UFS 版本。UFS 以其彈性在線擴容、穩(wěn)定可靠的特點,為眾多公有云、物理云、托管云用戶提供共享存儲方案,單文件系統(tǒng)存儲容量可達百 PB 級。
為了應對 IO 性能要求很高的數(shù)據(jù)分析、AI 訓練、高性能站點等場景,UFS 團隊又推出了一款基于 NVMe SSD 介質的性能型 UFS,以滿足高 IO 場景下業(yè)務對共享存儲的需求。性能型 UFS 的 4K 隨機寫的延遲能保持在 10ms 以下,4K 隨機讀延遲在 5ms 以下。
性能的提升不僅僅是因為存儲介質的升級,更有架構層面的改進,本文將從協(xié)議、索引、存儲設計等幾方面來詳細介紹性能型 UFS 升級改造的技術細節(jié)。
協(xié)議改進
此前容量型 UFS 設計時支持的協(xié)議為 NFSv3,其設計理念是接口無狀態(tài),故障恢復的邏輯簡單。此外 NFSv3 在 Linux 和 Windows 上被廣泛支持,更易于跨平臺使用。但是 NFSv3 的設計缺點導致的高延遲在高 IO 場景下是不可接受的,所以在性能型 UFS 中,我們選擇僅支持性能更好、設計更先進的 NFSv4 協(xié)議。
NFSv4 與 NFSv3 相比,更先進的特性包括:支持有狀態(tài)的 lock 語義、多協(xié)議間的 compound 機制等。特別是 compound 機制,可以讓多次 NFS 協(xié)議交互在一個 RTT 中完成,很好地解決了 NFSv3 性能低效的問題。一次典型的 open for write 操作,在 NFSv3 和 NFSv4 上分別是這樣的:
可以看到,在關鍵的 IO 部分,NFSv4 比 NFSv3 節(jié)省一半的交互次數(shù),可以顯著降低 IO 延遲。除了協(xié)議以外,性能型 UFS 的核心由業(yè)務索引和底層存儲兩部分組成,由于底層 IO 性能的提升,這兩部分都需要進行深度改造以適應這種結構性的改變。下面我們將分別介紹這兩部分的改造細節(jié)。
業(yè)務索引
索引服務是分布式文件系統(tǒng)的核心功能之一。相比對象存儲等其它存儲服務,文件存儲的索引需要提供更為復雜的語義,所以會對性能產(chǎn)生更大影響。
索引服務的功能模塊設計是基于單機文件系統(tǒng)設計思路的一種『仿生』,分為兩大部分:
??目錄索引: 實現(xiàn)樹狀層級目錄,記錄各個目錄下的文件和子目錄項
??文件索引: 記錄文件元數(shù)據(jù),包含數(shù)據(jù)塊存儲信息和訪問權限等
索引服務各模塊的功能是明確的,主要解決兩個問題:
??業(yè)務特性: 除了實現(xiàn)符合文件系統(tǒng)語義的各類操作外,還要保證索引數(shù)據(jù)的外部一致性,在各類并發(fā)場景下不對索引數(shù)據(jù)產(chǎn)生靜態(tài)修改從而產(chǎn)生數(shù)據(jù)丟失或損壞
??分布式系統(tǒng)特性: 包括系統(tǒng)拓展性、可靠性等問題,使系統(tǒng)能夠應對各類節(jié)點和數(shù)據(jù)故障,保證系統(tǒng)對外的高可用性和系統(tǒng)彈性等
雖然功能有區(qū)別,目錄索引和文件索引在架構上是類似的,所以我們下面只介紹文件索引 (FileIdx) 架構。在以上的目標指導下,最終 FileIdx 采用無狀態(tài)設計,依靠各索引節(jié)點和 master 之間的租約(Lease)機制來做節(jié)點管理,實現(xiàn)其容災和彈性架構。
租約機制和悲觀鎖
master 模塊負責維護一張路由表,路由表可以理解成一個由虛節(jié)點組成的一致性哈希環(huán),每個 FileIdx 實例負責其中的部分虛節(jié)點,master 通過心跳和各個實例節(jié)點進行存活性探測,并用租約機制告知 FileIdx 實例和各個 NFSServer 具體的虛節(jié)點由誰負責處理。如果某個 FileIdx 實例發(fā)生故障,master 只需要在當前租約失效后將該節(jié)點負責的虛節(jié)點分配給其他實例處理即可。
當 NFSServer 需要向文件服務請求具體操作 (比如請求分配 IO 塊) 時,會對請求涉及的文件句柄做哈希操作確認負責該文件的虛節(jié)點由哪個 FileIdx 處理,將請求發(fā)至該節(jié)點。每個節(jié)點上為每個文件句柄維持一個處理隊列,隊列按照 FIFO 方式進行執(zhí)行。本質上這構成了一個悲觀鎖,當一個文件的操作遇到較多并發(fā)時,我們保證在特定節(jié)點和特定隊列上的排隊,使得并發(fā)修改導致的沖突降到最低。
更新保護
盡管租約機制一定程度上保證了文件索引操作的并發(fā)安全性,但是在極端情況下租約也不能保持并發(fā)操作的絕對互斥及有序。所以我們在索引數(shù)據(jù)庫上基于 CAS 和 MVCC 技術對索引進行更新保護,確保索引數(shù)據(jù)不會因為并發(fā)更新而喪失外部一致性。
IO 塊分配優(yōu)化
在性能型 UFS 中,底層存儲的 IO 延遲大幅降低帶來了更高的 IOPS 和吞吐,也對索引模塊特別是 IO 塊的分配性能提出了挑戰(zhàn)。頻繁地申請 IO 塊導致索引在整個 IO 鏈路上貢獻的延遲比例更高,對性能帶來了損害。一方面我們對索引進行了讀寫分離改造,引入緩存和批量更新機制,提升單次 IO 塊分配的性能。
同時,我們增大了 IO 塊的大小,更大的 IO 數(shù)據(jù)塊降低了分配和獲取數(shù)據(jù)塊的頻率,將分配開銷進行均攤。后續(xù)我們還將對索引關鍵操作進行異步化改造,讓 IO 塊的分配從 IO 關鍵路徑上移除,最大程度降低索引操作對 IO 性能的影響。
底層存儲
存儲功能是一個存儲系統(tǒng)的重中之重,它的設計實現(xiàn)關系到系統(tǒng)最終的性能、穩(wěn)定性等。通過對 UFS 在數(shù)據(jù)存儲、數(shù)據(jù)操作等方面的需求分析,我們認為底層存儲 (命名為 nebula) 應該滿足如下的要求:?簡單:簡單可理解的系統(tǒng)有利于后期維護?可靠:必須保證高可用性、高可靠性等分布式要求?拓展方便:包括處理集群擴容、數(shù)據(jù)均衡等操作?支持隨機 IO?充分利用高性能存儲介質
Nebula: append-only 和中心化索引
基于以上目標,我們將底層存儲系統(tǒng) nebula 設計為基于 append-only 的存儲系統(tǒng) (immutable storage)。面向追加寫的方式使得存儲邏輯會更簡單,在多副本數(shù)據(jù)的同步上可以有效降低數(shù)據(jù)一致性的容錯復雜度。更關鍵的是,由于追加寫本質上是一個 log-based 的記錄方式,整個 IO 的歷史記錄都被保存,在此之上實現(xiàn)數(shù)據(jù)快照和數(shù)據(jù)回滾會很方便,在出現(xiàn)數(shù)據(jù)故障時,更容易做數(shù)據(jù)恢復操作。
在現(xiàn)有的存儲系統(tǒng)設計中,按照數(shù)據(jù)尋址的方式可以分為去中心化和中心化索引兩種,這兩者的典型代表系統(tǒng)是 Ceph 和 Google File System。去中心化的設計消除了系統(tǒng)在索引側的故障風險點,并且降低了數(shù)據(jù)尋址的開銷。但是增加了數(shù)據(jù)遷移、數(shù)據(jù)分布管理等功能的復雜度。出于系統(tǒng)簡單可靠的設計目標,我們最終選擇了中心化索引的設計方式,中心化索引使集群擴容等拓展性操作變得更容易。
數(shù)據(jù)塊管理:extent-based 理念
中心化索引面臨的性能瓶頸主要在數(shù)據(jù)塊的分配上,我們可以類比一下單機文件系統(tǒng)在這方面的設計思路。早期文件系統(tǒng)的 inode 對數(shù)據(jù)塊的管理是 block-based,每次 IO 都會申請 block 進行寫入,典型的 block 大小為 4KB,這就導致兩個問題:1、4KB 的數(shù)據(jù)塊比較小,對于大片的寫入需要頻繁進行數(shù)據(jù)塊申請操作,不利于發(fā)揮順序 IO 的優(yōu)勢。2、inode 在基于 block 的方式下表示大文件時需要更大的元數(shù)據(jù)空間,能表示的文件大小也受到限制。
在 Ext4/XFS 等更先進的文件系統(tǒng)設計中,inode 被設計成使用 extent-based 的方式來實現(xiàn),每個 extent 不再被固定的 block 大小限制,相反它可以用來表示一段不定長的磁盤空間,如下圖所示:
顯然地,在這種方式下,IO 能夠得到更大更連續(xù)的磁盤空間,有助于發(fā)揮磁盤的順序寫能力,并且有效降低了分配 block 的開銷,IO 的性能也得到了提升,更關鍵的是,它可以和追加寫存儲系統(tǒng)非常好地結合起來。我們看到,不僅僅在單機文件系統(tǒng)中,在 Google File System、Windows Azure Storage 等分布式系統(tǒng)中也可以看到 extent-based 的設計思想。我們的 nebula 也基于這一理念進行了模型設計。
Stream 數(shù)據(jù)流
在 nebula 系統(tǒng)中存儲的數(shù)據(jù)按照 stream 為單位進行組織,每個 stream 稱為一個數(shù)據(jù)流,它由一個或多個 extent 組成,每次針對該 stream 的寫入操作以 block 為單位在最后一個 extent 上進行追加寫,并且只有最后一個 extent 允許寫入,每個 block 的長度不定,可由上層業(yè)務結合場景決定。而每個 extent 在邏輯上構成一個副本組,副本組在物理上按照冗余策略在各存儲節(jié)點維持多副本,stream 的 IO 模型如下:
streamsvr 和 extentsvr
基于這個模型,存儲系統(tǒng)被分為兩大主要模塊:?streamsvr:負責維護各個 stream 和 extent 之間的映射關系以及 extent 的副本位置等元數(shù)據(jù),并且對數(shù)據(jù)調度、均衡等做管控?extentsvr:每塊磁盤對應一個 extentsvr 服務進程,負責存儲實際的 extent 數(shù)據(jù)存儲,處理前端過來的 IO 請求,執(zhí)行 extent 數(shù)據(jù)的多副本操作和修復等
在存儲集群中,所有磁盤通過 extentsvr 表現(xiàn)為一個大的存儲池,當一個 extent 被請求創(chuàng)建時,streamsvr 根據(jù)它對集群管理的全局視角,從負載和數(shù)據(jù)均衡等多個角度選取其多副本所在的 extentsvr,之后 IO 請求由客戶端直接和 extentsvr 節(jié)點進行交互完成。在某個存儲節(jié)點發(fā)生故障時,客戶端只需要 seal 掉當前在寫入的 extent,創(chuàng)建一個新的 extent 進行寫入即可,節(jié)點容災在一次 streamsvr 的 rpc 調用的延遲級別即可完成,這也是基于追加寫方式實現(xiàn)帶來的系統(tǒng)簡潔性的體現(xiàn)。
由此,存儲層各模塊的架構圖如下:
至此,數(shù)據(jù)已經(jīng)可以通過各模塊的協(xié)作寫入到 extentsvr 節(jié)點,至于數(shù)據(jù)在具體磁盤上的存儲布局,這是單盤存儲引擎的工作。
前面的存儲架構講述了整個 IO 在存儲層的功能分工,為了保證性能型 UFS 的高性能,我們在單盤存儲引擎上做了一些優(yōu)化。
線程模型優(yōu)化
存儲介質性能的大幅提升對存儲引擎的設計帶來了全新的需求。在容量型 UFS 的 SATA 介質上,磁盤的吞吐較低延遲較高,一臺存儲機器的整體吞吐受限于磁盤的吞吐,一個單線程 / 單進程的服務就可以讓磁盤吞吐打滿。隨著存儲介質處理能力的提升,IO 的系統(tǒng)瓶頸逐漸從磁盤往處理器和網(wǎng)絡帶寬方面轉移。
在 NVMe SSD 介質上由于其多隊列的并行設計,單線程模型已經(jīng)無法發(fā)揮磁盤性能優(yōu)勢,系統(tǒng)中斷、網(wǎng)卡中斷將成為 CPU 新的瓶頸點,我們需要將服務模型轉換到多線程方式,以此充分發(fā)揮底層介質多隊列的并行處理能力。為此我們重寫了編程框架,新框架采用 one loop per thread 的線程模型,并通過 Lock-free 等設計來最大化挖掘磁盤性能。
block 尋址
讓我們思考一個問題,當客戶端寫入了一片數(shù)據(jù) block 之后,讀取時如何找到 block 數(shù)據(jù)位置?一種方式是這樣的,給每個 block 分配一個唯一的 blockid,通過兩級索引轉換進行尋址:
?第一級:查詢 streamsvr 定位到 blockid 和 extent 的關系
?第二級:找到 extent 所在的副本,查詢 blockid 在 extent 內的偏移,然后讀取數(shù)據(jù)
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/117611.html