劉福鑫,李勁巍,王熠弘,李 琳
(武漢理工大學計算機科學與技術學院,武漢430070)
云計算已成為互聯網未來的趨勢之一,擁有不可估量的潛力[1]。云應用和云平臺的普及使開發者能夠將更多的精力投入于應用程序本身的開發與維護上,不用再關心硬件架構、服務器資源與日常硬件維護等與業務本身無關的雜項工作。近年來,越來越多的業務開始從傳統的服務器架構遷移到云上,同時我們也看到有更多的業務從設計之初即計劃運行在云端。這些業務通常被稱作云原生業務,意指其與云的高度結合。云原生業務相對傳統業務通常會更加充分且頻繁地應用例如微服務、彈性計算和服務編排等云平臺所提供的先進技術幫助業務更穩定、高效地運行。部分前沿實踐甚至嘗試使用更加輕量的操作系統(如Alpine Linux 等)來適應容器化和虛擬化技術[2]。
當前較為典型的云原生業務是移動互聯網時代的社交平臺,這些平臺大多充分利用了云計算的優勢,比如微博廣泛使用阿里云作為其計算后端,構建大規模混合云,將部分高頻業務運行于公有云平臺上,以靈活處理熱點事件的突發負載[3]。社交平臺的用戶量不斷增加,且對計算和存儲的需求極大,這不僅需要靈活而穩定的基礎架構來保障其穩定性,還要利用自動化擴容與災備等高級功能使其在突發負載下依舊為用戶提供良好的體驗[4]。
早在2010 年,Facebook 就已發表了其應對大規模照片與文件存儲的方案,這一方案被稱為Haystack。Haystack是一個專注于海量文件存儲的對象存儲系統,它充分滿足Facebook對文件的存儲需求。它使用文件塊將小文件打包進名為Superblock的物理卷中,并將元數據緩存在內存里以獲得極高的索引和查詢性能。它同樣支持集群化部署,能夠將存儲負載進行分散,以有效提升性能與可擴展性[5]。
隨著近些年來云計算的普及,互聯網展現出了整體向云上遷移的趨勢,以便充分享受云平臺所帶來的高效與穩定。但Haystack 及其大多數相似實現卻依舊被設計為運行在傳統硬件架構上,需要相對復雜的配置才能使其運行于云端,這與云原生開箱即用、輕量部署的理念有一定差距。若出現熱點事件或用戶量極速增長,手動擴容集群規模往往不能及時應對突發壓力,甚至在擴容過程中還會由于節點拓撲無法在線修改,引發不可避免的閃斷,降低服務可用性。此外,上述存儲方案在緩存與資源利用上的缺失,同樣導致了它們大多并不能充分利用云所能提供的資源與環境,所提供的性能依舊不夠理想,這一問題也影響了該類存儲方案的普及。
綜上所述,無論是傳統的文件系統還是當前的對象存儲系統,其設計與優化都不能充分滿足數據密集型業務在云上不斷擴張的需求,原因主要在于上述存儲方案大多都無法充分利用云平臺所提供的調度功能,適應負載的波動并利用以上功能實現自動化調度,進一步實現自我優化、自我管理。
為了解決以上問題,本文提出了一種新的存儲系統,稱之為Kubestorage,指運行在Kubernetes 上的存儲系統。該系統基于Haystack,但在服務發現和自動容錯等宏觀調度方面進行了專門優化,使其更符合云原生技術的定義,更適合部署在云端,也更適合于在存儲方面驅動數據密集型的云業務高效穩定運行;此外還針對Haystack 于磁盤方面的性能瓶頸,在緩存與易用性方面進行了優化。
學術界在對象存儲方面的研究要遠早于云計算。早在1996 年,卡內基梅隆大學就開始了由Garth Gibson 領導的相關研究,并于1998 年首次提出了對象存儲的概念與基礎架構[6]。此后,對象存儲在各存儲公司的支持下持續蓬勃發展。據統計,在1999 年到2013 年之間,在對象存儲方面的風險投資已突破3 億美元[7],這使得對象存儲技術得到了極大的發展,并迅速應用于大量成熟的產品中。
美國亞馬遜公司旗下的AWS(Amazon Web Services)早在2006年初就將對象存儲作為開放服務,開發了亞馬遜S3對象存儲服務。僅2012年一年,S3對象存儲系統就新增了超過一萬億個對象,而在短短一年后,甚至增加到每年新增兩萬億個對象。此外,S3 還能確保這些數據可以隨時被訪問與隨機讀寫。據亞馬遜官方報告,S3 中的任意一個文件都可以承載每秒最高110萬次的讀取[8]。
另一個相對較為知名的對象存儲解決方案是由Weil、Brandt、Miller、Long 和Maltzahn 在2006年USENIX 操作系統設計會議(OSDI 2006)中提出的Ceph[9]。Ceph 在軟件層面進行大量優化,能使用普通硬件實現大規模的文件存儲,相對于來自IBM、EMC等大型存儲方案公司的產品擁有更好的兼容性。
Facebook 公司在2010 年通過論文的形式提出了一種專注于大量小文件的存儲、更實用且更靈活的對象存儲模型,稱之為Haystack。Haystack 的獨特之處在于將小文件合并到一個稱為物理卷的文件塊中,以便在存儲大量小文件的同時保持較為理想的索引速度。文件索引則包含了文件的基礎信息、文件在文件塊中的偏移量等元數據。此外,文件索引被讀入內存中,以此提升索引速度,降低磁盤負擔。
Kubestorage存儲系統由三部分組成:
1)目錄服務器,用于節點信息的存儲、文件到存儲節點的映射、節點自動管理與負載均衡等功能。
2)存儲服務器,用于文件存儲、元數據管理和存儲一致性的自動維護。
3)緩存服務器,用于緩存高頻訪問的文件,以獲得更好的性能。
整個存儲系統運行在以Docker 為容器后端的Kubernetes集群之上,通過Kubernetes 配置文件的方式進行快速部署,Kubernetes負責提供管理、服務發現與故障恢復等功能。
Kubestorage 為解決Haystack 和其他存儲解決方案的缺點,進行了以下改進:
1)使用Raft 一致性算法[10]實現了對多目錄服務器的支持,使目錄服務器更穩定、可靠,避免單點故障造成整個存儲系統失效。
2)相較于Haystack,Kubestorage 并未將存儲服務器直接暴露給外部用戶或業務,而是使用反向代理由目錄服務器與業務進行讀取與寫入操作以提升安全性,同時增強目錄服務器對整體的控制能力。鑒于Kubernetes 環境下虛擬交換機帶寬大于傳統服務器互聯帶寬,且可以通過負載均衡器實現集群部署,因此相對于帶寬瓶頸,相對應的安全性與簡便性提升更有價值。
目錄服務器為業務提供單點API,涵蓋所有的讀寫與管理操作,以便簡化業務開發,避免存儲服務器暴露所導致的安全隱患。
3)定期巡檢所有文件,并自動壓縮長時間未訪問的數據,以節省存儲空間.
4)使用多級緩存機制以最大限度地降低檢索文件以及從磁盤中讀取文件造成的性能損失。
5)自動將經常訪問的文件復制到多個存儲服務器中,以分散文件讀取壓力,實現負載均衡。
此外,Kubestorage 還充分利用了容器化的特性和Kubernetes 所提供的豐富功能,保證了在不同軟硬件上運行環境的一致性,并在此基礎上使其具有如下更多功能:
1)動態監測所有Kubestorage 節點的狀態,并在磁盤空間、CPU配額、內存空間不足時自動擴容。
2)自動運行狀況檢查,其中包括一致性檢查、有效性校驗、延遲檢查、壓力測試與硬件健康檢查。
3)基于kube-apiserver 與kube-dns 的自動服務發現,可在服務不中斷的情況下動態修改存儲集群拓撲,提升Kubestorage的可用性。
Kubestorage 的詳細架構如圖1 所示,主要由目錄服務器、存儲服務器和緩存服務器三個部分組成。
目錄服務器主要承擔以下工作:
1)管理邏輯卷和物理卷之間的映射;
2)管理目錄服務器級別緩存;
3)當某個文件訪問量激增時,開啟自動文件冗余,在多個存儲服務器上建立該文件的副本以提升吞吐能力;
4)作為負載均衡器,對存儲服務器進行反向代理,并向外部業務提供統一的API。
同時,Kubestorage還具有以下功能,使其更適合自動化集群部署,并提供一致性與可靠性保障:
1)使用Raft 一致性算法提供較高的一致性與可靠性,避免單點故障導致整個存儲系統失效,外部業務對附屬節點的訪問將會被轉發到主節點。該算法保證了數據的一致性與可靠性,使得在每個輪換周期內有且僅有一個目錄服務器負責響應用戶請求。
2)Kubernetes 將會對每臺目錄服務器設置3 個預設標簽:角色、狀態和健康度。例如角色為kube-storage-directory,狀態為leader 或slave,運行狀況將是以下4 種類型之一:正在運行(Running)、正在初始化(Initializing)、忙碌(Busy)或離線(Offline)。當每個目錄服務器需要知道其他目錄服務器的狀態和數量時,可以使用這3 個標簽作為選擇器,向kubeapiserver 詢問目錄服務器列表,kube-apiserver 將返回集群內滿足所選擇條件的服務器。
3)為了保障數據一致性,避免使用讀/寫鎖造成系統邏輯復雜化,所有目錄服務器將會共享一個數據庫后端。數據庫后端的種類可以根據實際需求自由決定,但以Redis為例的內存數據庫通常可以提供最佳的性能。
此外,目錄服務器還會記錄最近的文件讀取請求并存儲在數據庫內該文件對應的記錄中,然后定期讀取上一個巡檢周期內各文件訪問次數。對于頻次超過設定閾值的文件,目錄服務器會使用存儲服務器的冗余寫入API 進行自動冗余,依靠在多個服務器上建立該文件的副本以提升性能,若下一次巡檢發現已冗余的文件訪問量低于閾值,目錄服務器則會使用冗余刪除API進行已冗余文件的移除。

圖1 Kubestorage的架構示意圖Fig.1 Schematic diagram of Kubestorage architecture
存儲服務器主要承擔以下工作:
1)負責管理存儲在其中的文件、文件的元數據以及文件的寫入和讀取;
2)管理存儲服務器級別緩存;
3)執行數據壓縮、解壓縮與有效性校驗;
4)定期向目錄服務器報告其狀態,以便根據統計信息進行自動化管理。
存儲服務器的技術實現如下:
1)元數據和物理卷的底層存儲與Haystack 基本一致,但Kubestorage 在此基礎上增加了對壓縮文件的支持,文件是否被壓縮的狀態被存儲在元數據中,并在寫入時壓縮存入,讀取時解壓讀取。
2)Kubernetes所提供的持久卷(Persistent Volume)將用作存儲服務器的存儲后端。它具有高度抽象的特性,獨立于特定的操作系統、存儲架構和硬件,可以與現有的存儲方案良好兼容,并可避免計算與存儲的強耦合。為了使Kubestorage 盡可能簡潔,冗余和高可用由實際的存儲后端自主實現,以充分利用磁盤/網絡存儲行業中長期的技術積累。
3)Kubestorage 選擇了較為松散的元數據存儲后端結構,以接口方式實現,使其能充分利用云平臺中所提供的數據庫服務進行元數據的存儲。此外,為了簡化部署和使用,Kubestorage搭載了leveldb[11]作為其默認數據庫。該數據庫在存儲量較小但訪問頻次較高、數據結構單一的情況下可以實現用很少的資源消耗提供相對理想的性能,避免普通文件數據庫頻繁磁盤讀寫與復雜存儲結構造成的性能瓶頸。
4)每個存儲服務器將定期運行自檢服務,獲取當前存儲容量、并發讀寫狀態、文件數量、硬件健康狀態、網絡情況以及其他所需信息,并提供API,便于外部查詢服務器狀態。自檢服務使得目錄服務器、Kubernetes、甚至運維人員可以隨時了解到當前每個節點的狀態并及時進行管理。
5)當具有副本標記的文件寫入請求到達存儲服務器時,存儲服務器會首先在本地存儲該文件。然后存儲服務器會根據寫入請求中所指定的地址向對應的其他存儲服務器發送副本創建請求,其他存儲服務器收到請求后同樣將文件寫入。副本創建請求同樣也可以被目錄服務器所觸發,以根據訪問頻次進行副本數的動態變化,實現動態負載均衡。
6)刪除文件時,存儲服務器會立即刪除元數據,但此時文件尚未被物理刪除。巡檢程序會定期啟動,以比較物理卷和元數據中的文件對應情況。如果發現當前物理卷有已刪除文件或損壞的文件,巡檢程序將創建新的臨時物理卷,并根據元數據將物理卷中的每個文件復制到臨時物理卷。
緩存服務器負責統一的數據緩存,由于需要存儲文件,因此使用Redis作為其存儲后端,從內存中進行文件讀寫以保障性能。緩存服務器使用了目錄/存儲兩級緩存設計,以提升緩存性能,盡量降低響應時間與服務器負載。
其具體的技術實現如下:
1)緩存服務器定期檢查存儲在其中的目錄與存儲級緩存,刪除重復的緩存(即緩存文件都存在于目錄級緩存和存儲級緩存中),并自動清除一段時間內未訪問的緩存以節省內存空間。
2)緩存服務器還會將頻繁訪問但只緩存在存儲級緩存中的文件移動到目錄級緩存中,以提升請求效率。
將文件上傳到Kubestorage 的過程分為以下幾步:目錄服務器首先接收來自業務的文件上傳請求,其中包括文件的副本次數和文件的二進制數據。如果當前目錄服務器不是Leader 服務器,則受理上傳請求的目錄服務器將向Leader 服務器發送一個反向代理請求。Leader 服務器將會核對數據庫,以確保物理卷數量足夠,然后創建區塊和存儲數據的請求將被發送到指定的存儲服務器。目錄服務器最后生成對應鏈接以便后續訪問。
從Kubestorage 下載文件的過程則分為以下幾步:首先業務將向目錄服務器發送文件下載請求,請求中包含此前所生成的永久鏈接,目錄服務器首先將檢查文件是否存在于目錄級緩存中,如果文件存在,數據將從內存中讀取,并直接傳輸給業務;如果未命中緩存,則會將文件獲取請求發送到相應的存儲服務器,存儲服務器同樣將檢查文件是否存在于存儲級緩存中,如果在存儲級緩存中則直接傳輸,如果緩存未命中,則存儲服務器會將當前文件放在存儲級緩存中,以此減少下次讀取時間,如果頻繁訪問該文件,它還會被轉移到目錄級緩存進一步提高文件的訪問速度。
存儲服務器會將接收到的每一次讀取請求記錄在數據庫中(每次巡檢周期結束后清空一次計數),并在下一次巡檢周期開始時將滿足訪問次數閾值的文件放置于相對應的存儲級緩存中。對于已緩存的文件,若下一次巡檢的讀取次數低于閾值,則將其從緩存中清除。若多次巡檢發現緩存依然未被清除,則將該文件轉移至目錄級緩存。
若緩存服務器緩存體積超過最高安全閾值,則會按照上一個巡檢周期內的緩存命中次數對文件進行排序,清除命中次數較低的文件直至緩存體積低于最低安全閾值。
上文所述所有閾值與巡檢周期均可配置,以便平衡性能與成本,適配不同規模的存儲需求。
Kubestorage 能容許服務器節點的故障并能實現自動恢復。每臺目錄服務器都會通過kube-apiserver 組件定時獲取當前所有節點狀態,若發現某臺服務器出現異常(即Kubernetes 自檢不成功,Pod 狀態為NotReady 或狀態標簽為Offline),目錄服務器會立刻調用kube-apiserver 清除故障節點并完成持久卷綁定、數據庫初始化等操作,實現自恢復。該策略對目錄服務器、存儲服務器與緩存服務器均有效。
由于負載均衡的存在,所有請求均會被傳遞到正常的目錄服務器中,若出現異常的目錄服務器為Raft 集群的Leader,則剩余的所有Followers會重新進行選舉。選舉期間的請求將會被放入等待隊列,并在對應存儲服務器恢復后立刻繼續任務。對故障緩存服務器的請求被視為緩存未命中,直接從存儲服務器請求所需文件。
性能分析部分將使用阿里云的ECS 服務器作為測試平臺,配置為Intel Xeon Platinum 8163 處理器(8 個虛擬核心),32 GB 內存,同時掛載6 TB 的SSD 云磁盤作為測試用數據盤(標稱IOPS為25 000,吞吐量為讀/寫均256 MB/s)。
我們選擇了EXT4、ZFS 和SeaweedFS 作為基準,分別代表傳統文件系統、現代文件系統和傳統對象存儲系統。SSD 云磁盤將分別被格式化為EXT4、ZFS 文件系統,并對其進行一系列的測試。該云磁盤也將被格式化為EXT4,并在其上配置Kubernetes 單機集群(使用Flannel 虛擬網絡),以便使用Kubestorage和SeaweedFS進行測試。
測試方法如下:在測試服務器上運行使用Golang 開發的文件讀寫API,并使用另一臺配置相同的服務器在局域網內遠程訪問此API,避免測試工具本身對測試造成的干擾。Kubestorage 將構建于Kubernetes 上,使用2 個目錄服務器、8個存儲服務器與1 個緩存服務器構成小型的Kubestorage 集群,物理卷大小為每文件1 GB,文件默認副本數為2。SeaweedFS 將保持與Kubestorage 相同的硬件配置與環境配置,并按照使用文檔使其運行于Kubernetes上。
測試過程如下:
1)提前分別將1 000 萬個文件大小為4 KB、40 KB 與400 KB 的文件寫入待測試的存儲系統,文件內容為隨機ASCII字符。為避免EXT4由于inode不足造成測試失敗,已提前擴增inode容量。
2)在另一臺服務器上(兩臺服務器之間網絡帶寬為10 Gb/s)通過測試軟件分別啟動足量線程直至測試服務器報告其正在執行的并發請求數量為50、500、1 000、5 000(避免由于發起線程數與測試服務器收到請求數不一致對測試造成干擾,使接收請求數盡量接近實際的并發讀取數),使用上文所提及統一的API 從測試服務器中隨機進行文件的讀取和寫入,在發起請求的服務器上記錄每次讀取和寫入的響應時間以供后續性能分析。每輪讀取測試中,1 000萬個文件中的隨機20 萬個將被以隨機的不同頻次總共讀取500 萬次,以模擬云計算環境中真實業務中的負載場景。每輪寫入測試中,將寫入20 萬個文件,每個文件大小分別為4 KB、40 KB 與400 KB,文件內容同樣為隨機ASCII。
3)從預寫入的1 000 萬個40 KB 文件中隨機選取100 個40 KB 文件進行熱點測試,模擬社交平臺中常見的熱點圖片資源,對這100 個文件同時進行并發數為1 000 的隨機讀取,每個文件讀取10 000次,記錄讀取過程中性能的變化、緩存的變化與文件分布的變化,以測試上文所述緩存策略與自動冗余策略是否有效。
4)在1 000個線程的40 KB隨機讀寫測試過程中,手動將一半的目錄服務器與一半的存儲服務器狀態配置為Offline(拒絕所有請求),以測試性能波動與服務穩定性。
由于Kubestorage 與SeaweedFS 均不支持目錄功能,為保證測試的準確性與可信度,對于傳統文件系統,所有讀取與寫入的文件均放置在同一目錄下。
在測試過程中,將記錄并比較平均響應時間、響應時間方差和1 000并發下響應時間占比(即小于特定響應時間的請求占比)。平均時間越短,表示性能越好;方差越小,表示越穩定;響應時間占比中低延時所占百分比越多表示性能越好。每組實驗將在相同條件下重復5 次,并取所有數據的平均值。實驗結果如表1~3所示。

表1 不同線程數量下各存儲方案的平均讀取時間、平均寫入時間與方差對比Tab.1 Comparison of average read time,average write time and their variances of different storage schemes under different thread number

表2 1 000并發請求下各存儲方案的讀取與寫入時間占比Tab.2 Average read and write time proportion of different storage schemes under 1 000 concurrent requests

表3 熱點訪問測試中平均響應時間與方差 單位:msTab.3 Average response time and variance in hot spot access test unit:ms
3.5.1 性能分析
從表1、2 中可以看出,ZFS 受限于其復雜的設計,響應時間與穩定性落后于其他存儲方案,這是由于ZFS相較于性能,更側重于可管理性與可擴展性,導致文件讀寫過程相對復雜,相應的資源耗費更多。當并發讀取量較低時,Kubestorage、SeaweedFS 的讀取性能與EXT4 大致相同。當并發讀取量上升后,Kubestorage 開始逐漸展現出其優勢,盡管Kubestorage和SeaweedFS 具有相同的存儲模型,但在讀取時,Kubestorage受益于其多級內存緩存與自動建立副本的設計,相對SeaweedFS 性能更占優勢。從表中可以看出,Kubestorage 在1 000 與5 000 并發時的平均響應時間與穩定性相對于SeaweedFS 與EXT4 均占有一定優勢。總體而言,采用Haystack 存儲結構的Kubestorage 與SeaweedFS 性能均優于EXT4 和ZFS,其中Kubestorage 由于緩存與自動副本策略的引入,性能相對更優。
從表1、2 中還可以看出,由于創建元數據和將文件插入物理卷的開銷較大,Kubestorage 和SeaweedFS 的寫入性能均遠低于其他文件系統,這一問題在請求數量增多時格外明顯。
從表3 中可以看出,在熱點訪問測試過程中,由于EXT4、ZFS、SeaweedFS 均不帶有文件緩存,在不同測試階段的性能并無顯著變化,但Kubestorage 隨著訪問計數的不斷增加開始逐漸開啟副本冗余(10 min 內500 次讀取觸發)、寫入緩存(10 min 內1 000 次讀取觸發),以降低存儲服務器的磁盤壓力。表3數據充分證明了緩存策略為Kubestorage 帶來的性能與穩定性提升。
3.5.2 穩定性分析
在手動關閉服務器的測試過程中,被關閉的目錄服務器是當前Raft集群的Leader,Kubernetes 的負載均衡將所有請求移交給另一臺Follower,Follower 在節點檢測過程中檢測到Leader 不可用,立刻開始新一輪選舉,并將自己設置為Leader。期間正在處理的請求被放入目錄服務器隊列,并在選舉完成后繼續請求。隨后新的Leader在選舉完成后立即調用kube-apiserver 新建了一臺新的目錄服務器以填補空缺,該服務器成為Follower,服務恢復正常。
被關閉的一半存儲服務器在節點檢測過程中被目錄服務器判斷為不可用,并由目錄服務器調用kube-apiserver 新建了4 臺新的存儲服務器,自動完成持久卷掛載與數據初始化操作,然后取出所需文件傳輸給客戶端。由于存儲服務器的初始化需要一定時間,關閉后約20 s內出現了一定的性能波動,這20 s 內的平均讀取響應時間由10.7 ms 增加到1 466.1 ms,但由于默認副本數為2,只有少部分文件完全不可讀。
綜上所述,本文所提出的Kubestorage 存儲系統被設計為更適合存儲云平臺與云業務中的大量小文件,尤其是以社交網絡為典型,即讀遠多于寫的使用場景。上述測試充分展現了Kubestorage 在架構設計上的優勢與潛力,且部署過程簡單,使用預配置的腳本僅需數條命令即可搭建出任意規模的Kubestorage存儲集群,這使其更為靈活,在云上擁有比傳統存儲解決方案更好的性能和更優秀的穩定性。Kubestorage開箱即用,更適合云原生業務部署,為云上的應用提供更快捷穩定的存儲解決方案。
但同時我們也看到了目前Kubestorage 所存在的一些不足,將來還需要在以下方面進行改進,以提供更好的使用體驗,進一步提升其實用性與易用性:
1)使用緩存服務器對文件寫入進行緩存,以實現異步文件保存功能,提供更好的寫入性能;
2)添加對目錄、文件別名、軟鏈接等高級特性的支持,以保持與傳統文件系統的雙向兼容和相互轉換,提升兼容性與靈活度,便于讓更多業務享受對象存儲的便利;
3)考慮到目標應用場景內可能有大量重復的文件資源,因此可對散列相同的文件進行合并存儲,以節省磁盤空間。