Crimson:高性能,高擴(kuò)展的新一代 Ceph OSD
? 背景
隨著物理硬件的不斷發(fā)展,存儲軟件所使用的硬件的情況也一直在不斷變化。
一方面,內(nèi)存和 IO 技術(shù)一直在快速發(fā)展,硬件的性能在極速增加。在最初設(shè)計 Ceph 的時候,通常情況下,Ceph 都是被部署到機(jī)械硬盤上,能夠提供數(shù)百 IOPS 的讀寫和數(shù)十 G 的磁盤容量。但是,目前最新的 NVMe 設(shè)備可以提供數(shù)百萬的 IOPS 讀寫,并支持 TB 級的磁盤容量。DRAM 的容量在大約20年的時間內(nèi)增加了128倍。對于網(wǎng)絡(luò) IO 來說,網(wǎng)卡設(shè)備現(xiàn)在能夠提供超過 400Gbps 的速度,而幾年前只有 10Gbps。
另一方面,在大約十年的時間內(nèi),CPU 頻率和 CPU 內(nèi)核的單線程性能一直處于穩(wěn)定的狀態(tài),增長不明顯。相比之下邏輯核心的數(shù)量隨著晶體管規(guī)模的增加而迅速增長。
Ceph 的性能要跟上硬件發(fā)展的速度一直很有挑戰(zhàn)的,因為 Ceph 的架構(gòu)是十年前的——它對單核 CPU 性能的依賴使它無法充分利用不斷增長的 IO。特別是,當(dāng) Ceph 對象存儲守護(hù)程序(OSD)依賴線程池來處理不同的 IO 時,跨 CPU 核心通信會產(chǎn)生了大量的延遲開銷。減少或消除這些開銷成本是 Crimson 項目的核心目標(biāo)。
Crimson 項目使用 shared-nothing? 設(shè)計和 run-to-completion 模型來重寫 Ceph OSD,以滿足苛刻的硬件與軟件系統(tǒng)的擴(kuò)展要求,同時也與現(xiàn)有的客戶端和組件兼容。
為了理解 Crimson OSD 如何針對 CPU 擴(kuò)展進(jìn)行重新設(shè)計的,我們比較了 傳統(tǒng) OSD 和 Crimson OSD 之間的架構(gòu)差異,來解釋架構(gòu)怎么以及為何這樣設(shè)計。然后我們討論了 Crimson 為什么建立在 Seastar 框架之上,以及每個核心組件是如何實(shí)現(xiàn)擴(kuò)展的。
最后,我們分享了實(shí)現(xiàn)這一目標(biāo)的最新情況,同時還提供了一個我們最終希望達(dá)到的性能結(jié)果。
Crimson 與傳統(tǒng) OSD 架構(gòu)
Ceph OSD 是 Ceph 集群的一部分,主要負(fù)責(zé)通過網(wǎng)絡(luò)來提供對象的訪問、維護(hù)數(shù)據(jù)冗余和高可用性以及將對象持久化到本地存儲設(shè)備。作為傳統(tǒng) OSD 的重寫版本,Crimson OSD 從客戶端和 OSD 的角度來看是與現(xiàn)有的 RADOS 協(xié)議兼容的,它提供相同的接口和功能。Messenger、OSD 服務(wù)和 ObjectStore 等 Ceph OSD 模塊化的功能沒有太大改變,但跨組件交互和內(nèi)部資源管理的形式進(jìn)行了大幅重構(gòu),以使用 shared-nothing 設(shè)計和自下而上的用戶空間任務(wù)調(diào)度。
傳統(tǒng) OSD 的架構(gòu)中,每個組件中都有線程池,針對多 CPU 核心場景下,使用共享隊列處理任務(wù)效率很低。在一個簡單的例子中,一個 PG 操作需要先由一個messenger worker 線程處理,將原始數(shù)據(jù)流組裝或解碼成一條消息,然后放入消息隊列中進(jìn)行調(diào)度。之后由一個PG worker thread 來獲取消息,經(jīng)過必要的處理后,將請求以事務(wù)的形式交給 ObjectStore。
事務(wù)提交后,PG 會完成操作,再次通過發(fā)送隊列和 messenger worker 線程發(fā)送回復(fù)。盡管可以通過向池中添加更多線程來將工作負(fù)載擴(kuò)展到多個 CPU,但這些線程默認(rèn)共享資源,因此需要鎖,這會引入爭用問題。
傳統(tǒng)架構(gòu)的一個主要挑戰(zhàn)是鎖競爭開銷隨著任務(wù)數(shù)和 CPU 核數(shù)的增加而迅速擴(kuò)大,在某些場景下每個鎖點(diǎn)都可能成為擴(kuò)展瓶頸。此外,這些鎖和隊列即使在沒有爭用的情況下也會產(chǎn)生延遲開銷。多年來,社區(qū)在分析和優(yōu)化更細(xì)粒度的資源管理和快速路徑實(shí)現(xiàn)以跳過隊列方面做了大量工作。未來,這類優(yōu)化的成果會越來越少,可擴(kuò)展性似乎會在當(dāng)前的設(shè)計架構(gòu)下達(dá)到了某個瓶頸。也還有其他挑戰(zhàn)。隨著在工作線程之間分配任務(wù),延遲問題將隨著線程池和任務(wù)隊列而惡化。鎖可以強(qiáng)制上下文切換,這會使事情變得更糟。
Crimson 項目希望通過 shared-nothing? 設(shè)計和 run-to-completion 模型來解決 CPU 可擴(kuò)展性問題。該設(shè)計的重點(diǎn)是強(qiáng)制每個內(nèi)核或 CPU 運(yùn)行一個固定線程并在用戶空間中分配非阻塞任務(wù)。因為請求以及它們的資源可以被分配到各個核心,所以它們可以在同一個核心中被處理,直到處理完成。理想情況下,我們不再需要所有的鎖和上下文切換,因為每個正在運(yùn)行的非阻塞任務(wù)都使用到 CPU,一直到它完成任務(wù)。沒有其他線程可以在同一時間搶占任務(wù)。如果不需要與數(shù)據(jù)路徑中的其他分片通信,理想情況下,性能將隨著內(nèi)核數(shù)量線性擴(kuò)展,直到 IO 設(shè)備達(dá)到其極限。這種設(shè)計非常適合 Ceph OSD,因為在 OSD 層面,所有 IO 都已經(jīng)被 PG 分片了。
雖然跨區(qū)通信不能完全消除,但那通常是用于 OSD 全局狀態(tài)的維護(hù),而不是用于數(shù)據(jù)路徑中。這里的一個主要挑戰(zhàn)是,最重要的改變是對 OSD 操作的基本要求——相當(dāng)一部分現(xiàn)有的鎖或線程代碼無法重用,需要重新設(shè)計,同時保持向后的兼容性。
重新設(shè)計需要對代碼的整體理解,以及相關(guān)的注意事項。使用 shared-nothing? 架構(gòu)實(shí)現(xiàn)底層的one-thread-per-core和用戶空間調(diào)度是另一個挑戰(zhàn)。
Crimson 試圖在 Seastar 的基礎(chǔ)上重新設(shè)計 OSD,Seastar 是一個異步編程框架,具有滿足上述目標(biāo)的所有理想特性。
Seastar Framework
Seastar 是 Crimson 項目的理想選擇,因為它不僅在 C++ 中實(shí)現(xiàn)了 one-thread-per-core? 的 shared-nothing 架構(gòu),而且還提供了一套全面的功能和模型,這些功能和模型已被證明在其它應(yīng)用程序中對性能和擴(kuò)展有效。資源默認(rèn)情況下不在分片之間共享,Seastar 實(shí)現(xiàn)了自己的內(nèi)存分配器以進(jìn)行無鎖分配。該分配器還利用了 NUMA 拓?fù)浣Y(jié)構(gòu)的優(yōu)勢,將最近的內(nèi)存分配給分片。對于一些不可避免的跨核資源共享和通信,Seastar 強(qiáng)制要求明確地處理它們。如果一個分片擁有另一個核心的資源,它必須通過外部指針指向這些資源;如果一個分片需要與其他分片通信,它必須提交并轉(zhuǎn)發(fā)任務(wù)給他們。這就迫使程序限制其跨核的需求,并有助于減少對 CPU 擴(kuò)展性問題的分析范圍。Seastar 還為跨核通信實(shí)現(xiàn)了高性能的非阻塞通信。
傳統(tǒng)的帶有異步事件和回調(diào)的程序在實(shí)現(xiàn)、理解和調(diào)試方面是非常困難的。用戶空間的非阻塞任務(wù)調(diào)度需要實(shí)現(xiàn)普遍的異步性。Seastar 將 futures、promises 和 continuations (f/p/c) 作為構(gòu)建塊來組織邏輯。futures 和 promises 通過將邏輯上連接的異步結(jié)構(gòu)組合在一起,而不是將它們分散用于普通的回調(diào)中,這使代碼更更容易實(shí)現(xiàn)以及更好的可讀性。Seastar 還為循環(huán)、計時器以及基于未來控制生命周期甚至 CPU 份額提供了更高級別的工具。為了進(jìn)一步簡化應(yīng)用程序,Seastar 將網(wǎng)絡(luò)和磁盤訪問封裝到 shared-nothing 和基于 f/p/c 設(shè)計的模式中。采用不同 I/O 堆棧(如 epoll、linux-aio、io-uring、DPDK 等)的復(fù)雜性和細(xì)微控制對應(yīng)用程序代碼是透明的。
Run-to-completion performance
Crimson 團(tuán)隊已經(jīng)為 RBD 客戶端的讀寫工作負(fù)載實(shí)現(xiàn)了 OSD 的大部分關(guān)鍵特性。當(dāng)前完成的任務(wù)包括重新實(shí)現(xiàn) messenger V2 (msgr2), heartbeat, PG peering, backfill, recovery, object-classes, watch-notify, etc等,并不斷努力的增加一些 CI 測試組件。Crimson 已經(jīng)達(dá)到了一個里程碑,我們可以在具有足夠穩(wěn)定的單個分片中驗證run-to-completion設(shè)計。
綜合考慮現(xiàn)實(shí)條件,在相同的隨機(jī) 4KB RBD 工作負(fù)載下,在沒有復(fù)制的情況下,通過將傳統(tǒng)和 Crimson OSD 與 BlueStore 后端進(jìn)行比較來驗證 single-shard run-to-completion?。兩個 OSD 都分配了 2 個 CPU 資源。Crimson OSD 很特別,因為 Seastar 需要一個獨(dú)占 CPU 核心來運(yùn)行 single-shard OSD 邏輯。這意味著 BlueStore 線程必須固定到另一個核心,引入 AlienStore 來彌合 Seastar 線程和 BlueStore 線程之間的邊界,并在兩個邊界之間提交 IO 任務(wù)。相比之下,傳統(tǒng) OSD 沒有限制使用分配的 2 個 CPU。
性能結(jié)果顯示,使用 BlueStore 時,Crimson OSD 的隨機(jī)讀取性能大約提高了 25%,隨機(jī)寫入情況下的 IOPS 大約比傳統(tǒng) OSD 高 24%。進(jìn)一步的分析顯示,在隨機(jī)寫的情況下,CPU 的利用率很低,因為大約 20% 的 CPU 被消耗在頻繁的查詢中,這表明 Crimson OSD 應(yīng)該不是是當(dāng)前的瓶頸。
Crimson OSD 提交和完成 IO 任務(wù),以及在 Seastar 和 BlueStore 線程之間進(jìn)行同步,也有額外的開銷。因此,我們針對 MemStore 后臺重復(fù)了同一組實(shí)驗,兩個 OSD 都分配了 1 個 CPU。如下圖所示,Crimson OSD 在隨機(jī)讀取中提供了大約 70% 的 IOPS,在隨機(jī)寫入中比 傳統(tǒng) OSD 高 25%,這與之前實(shí)驗中的結(jié)論一致,即 Crimson OSD 可以做得更好。
盡管上述場景僅涵蓋實(shí)驗性 single-shard? 案例,但結(jié)果表明使用 Seastar 框架具有性能優(yōu)勢——消除鎖、通過用戶空間任務(wù)調(diào)度刪除上下文切換、分配更靠近 CPU 的內(nèi)存。此外,重要的是要重申,run-to-completion 模型的目標(biāo)是更好地擴(kuò)展 CPU 并消除軟件使用高性能硬件而引起的性能瓶頸。
Multi-shard Implementation
實(shí)現(xiàn)多分片的路徑很明確。由于每個PG中的 IO 已經(jīng)在邏輯上被分片,所以對IO路徑?jīng)]有太大改變。主要的挑戰(zhàn)是確定無法避免的跨核通信,并設(shè)計新的解決方案,以盡量減少其對IO路徑的影響,這需要根據(jù)具體情況進(jìn)行分析。一般來說,當(dāng)從 Messenger 接收到一個 IO 操作時,它會根據(jù) PG-core 映射被定向到 OSD 分片,并在同一分片/CPU的上下文中運(yùn)行,直到完成。請注意,在當(dāng)前階段,為了簡單起見,設(shè)計上選擇不修改RADOS協(xié)議。
Messenger
Messenger 在確保解決方案可擴(kuò)展方面發(fā)揮著重要作用。有一些限制需要認(rèn)真考慮。一個限制來自 RADOS 協(xié)議,它只為每個客戶端或 OSD 定義一個連接。連接必須存在于特定核心上才能根據(jù)其狀態(tài)高效且無鎖地解碼和編碼消息。與 OSD 對等體的共享連接意味著在當(dāng)前階段跨核消息傳遞到多個 PG 分片是不可避免的,除非可以調(diào)整協(xié)議以允許到每個分片的獨(dú)占連接。
Seastar 框架的另一個限制是它不允許在 Seastar 套接字被 accept()ed? 或 connect()ed 之后移動到另一個核心。這對無損連接 (msgr2) 來說是一個挑戰(zhàn),因為它會影響 Messenger 和 OSD 服務(wù)之間的交互,在這種情況下,由于網(wǎng)絡(luò)故障重新連接,連接可能會預(yù)先跳轉(zhuǎn)到另一個核心。
擴(kuò)展 Messenger 的大部分工作是在將 IO 操作分派到 PG 分片之前將消息傳遞工作負(fù)載(編碼、解碼、壓縮、加密、緩沖區(qū)管理等)優(yōu)化擴(kuò)展到多個內(nèi)核,并最小化跨內(nèi)核消息沿 IO 路徑傳遞,理想情況下,在上述約束下,對于每個消息發(fā)送和接收操作,它最多保持 1 跳。
OSD
OSD 負(fù)責(zé)維護(hù) PG 分片之間共享的全局狀態(tài)和活動,包括心跳、身份驗證、客戶端管理、osdmap、PG 維護(hù)、訪問 Messenger 和 ObjectStore 等。
多核 Crimson OSD 的一個簡單原則是將所有與共享狀態(tài)相關(guān)的處理保持在專用內(nèi)核上。如果一個 IO 操作要訪問共享資源,要么按順序訪問專用核,要么訪問保持同步的共享信息的獨(dú)占副本。
實(shí)現(xiàn)這一目標(biāo)有兩個主要步驟。第一步是讓 IO 操作根據(jù) PG 分片策略運(yùn)行在多個 OSD 分片中,包括 PG 狀態(tài)在內(nèi)的所有全局信息都維護(hù)在第一個分片中。此步驟在 OSD 中啟用分片,但需要在第一個分片中做出有關(guān) IO 調(diào)度的所有決策。即使這一步 Messenger 可以在多核中運(yùn)行,消息仍然需要傳遞到第一個分片進(jìn)行準(zhǔn)備(例如 PG peering)并在提交到該分片之前確定正確的 PG 分片。這會導(dǎo)致額外的開銷和不平衡的 CPU 使用(第一個 OSD 分片使用率高,其他分片很低,等等)。因此,下一步是將 PG-core 映射擴(kuò)展到所有 OSD 分片。
ObjectStore
Crimson 支持三種 ObjectStore 后端:AlienStore、CyanStore 和 SeaStore。AlienStore 提供與 BlueStore 的向后兼容性。CyanStore 是用于測試的虛擬后端,由易失性內(nèi)存實(shí)現(xiàn)。SeaStore 是一種新的對象存儲,專為 Crimson OSD 設(shè)計,采用 shared-nothing 設(shè)計。根據(jù)后端的具體目標(biāo),實(shí)現(xiàn)多分片支持的路徑是不同的。
1AlienStore
AlienStore 是 Seastar 線程中的一個瘦代理,用于與使用 POSIX 線程的 BlueStore 進(jìn)行通信。對于多個 OSD 分片沒有特別的工作要做,因為 IO 任務(wù)通信同步了。BlueStore 中沒有為 Crimson 定制其他內(nèi)容,因為不可能真正將 BlueStore 擴(kuò)展到 shared-nothing 設(shè)計,因為它依賴于第 三 方 RocksDB 項目,而 RocksDB 仍然是線程的。但是,在 Crimson 能夠拿出一個足夠優(yōu)化和足夠穩(wěn)定的原生存儲后端解決方案(SeaStore)之前,合理的開銷來換取復(fù)雜的存儲后端解決方案是可以接受的。
2CyanStore
Crimson OSD 中的 CyanStore 與傳統(tǒng) OSD 中的 MemStore 相對應(yīng)。對多分片支持的唯一改變是為每個分片創(chuàng)建獨(dú)立的 CyanStore 實(shí)例。一個目標(biāo)是確保虛擬 IO 操作能夠在同一個內(nèi)核中完成,以幫助識別 OSD 級別的可擴(kuò)展性問題(如果有的話)。另一個目標(biāo)是在 OSD 層面上與傳統(tǒng) OSD 做直接的性能比較,而不受 ObjectStore 的復(fù)雜因數(shù)影響。
3SeaStore
SeaStore 是 Crimson OSD 原生的 ObjectStore 解決方案,采用 Seastar 框架開發(fā),采用相同的設(shè)計原則。
雖然很有挑戰(zhàn)性,但是 Crimson 必須建立一個新的本地存儲引擎,這有多種原因。存儲后端是主要的 CPU 資源消耗者,如果 Crimson OSD 的存儲后端不改變,那么它就不能真正地隨核心擴(kuò)展。我們的實(shí)驗也證明了 Crimson OSD 不是隨機(jī)寫入場景中的瓶頸。
其次,BlueStore 中具有事務(wù)支持的 CPU 密集型元數(shù)據(jù)管理基本上由 RocksDB 提供,如果不重新實(shí)現(xiàn),它無法在原生的 Seastar 線程中運(yùn)行。與其為 BlueStore 重新實(shí)現(xiàn)通用的鍵值事務(wù)存儲,不如在更高的層次上重新思考和定制相應(yīng)的架構(gòu)——ObjectStore。問題在原生的解決方案中比在 第三方項目中更容易解決,因為第三方項目必須保證使用與通用的場景。
第三個考慮是為異構(gòu)存儲設(shè)備和硬件加速器提供原生支持,讓用戶可以根據(jù)自己的需求平衡成本和性能。如果 Crimson 能夠更好地控制整個存儲堆棧,那么 Crimson 將更靈活地簡化部署硬件組合的解決方案。
SeaStore 在單分片讀寫方面已經(jīng)可以正常使用,盡管在穩(wěn)定性和性能改進(jìn)方面仍有待努力。目前的努力仍然集中在架構(gòu)上,而不是極端情況下的優(yōu)化。它針對多分片 OSD 的設(shè)計很明確。與 CyanStore 一樣,第一步是為每個 OSD 分片創(chuàng)建獨(dú)立的 SeaStore 實(shí)例,每個實(shí)例都在存儲設(shè)備的靜態(tài)分區(qū)上運(yùn)行。第二步是實(shí)現(xiàn)一個共享磁盤空間平衡器來動態(tài)調(diào)整分區(qū),它應(yīng)該可以在后臺異步運(yùn)行,因為 PG 已經(jīng)以偽隨機(jī)方式分配了用戶 IO。SeaStore 實(shí)例可能不需要等于 OSD 分片的數(shù)量,根據(jù)性能分析,調(diào)整這個比例是后期工作的第三步。
摘要和測試配置
在這篇文章中,我們介紹了為什么以及如何對 Ceph OSD 進(jìn)行重構(gòu)以跟上硬件的發(fā)展。另外我們也給出了我們所做的詳細(xì)設(shè)計、 一個簡單的性能測試結(jié)果。也提供了 Crimson OSD 真正實(shí)現(xiàn)多核可擴(kuò)展的所要考慮的大部分因素。
測試結(jié)果可能會根據(jù)不同的 commit 版本、軟件和硬件配置而有所變化。為了確保我們的測試是可重復(fù)的,可復(fù)現(xiàn)的,并可在以后場景中作為參考,我們列出了所有可能產(chǎn)生影響的設(shè)置和注意事項。
我們?yōu)?Crimson 和 傳統(tǒng) OSD 部署了本地 Ceph 集群,并使用 CBT 執(zhí)行了 FIO 測試。Crimson 在使用 tcmalloc 時仍然存在問題,因此為了公平起見,我們將兩個 OSD 配置為使用 libc*。我們使用 BlueStore。RBD 緩存被禁用。BlueStore 線程數(shù)設(shè)置為 4 以獲得更好的結(jié)果。部署 Crimson 時,需要指定*ceph-osd_cmd ( crimson-osd )。CPU 綁定通過 CBT 配置文件中的 crimson_cpusets 指定,BlueStore 線程通過 crimson_alien_thread_cpu_cores 和 crimson_alien_op_num_threads 配置。要部署傳統(tǒng) OSD,numactl 用于控制 CPU 綁定。根據(jù) CBT 存儲庫,部署過程的其余部分沒有變化。
測試場景:
- Client: 4 FIO clients
- IO mode: random write and then random read
- Block size: 4KB
- Time: 300s X 5 times to get the average results
- IO-depth: 32 X 4 clients
- Create 1 pool using 1 replica
- 1 RBD image X 4 clients
- The size of each image is 256GB
測試環(huán)境:
- Ceph 版本 (SHA1):7803eb186d02bb852b95efd1a1f61f32618761d9
- Ubuntu 20.04
- GCC-12
- 1TB NVMe SSD 作為 BlueStore 塊設(shè)備
- 50GB 內(nèi)存用于 MemStore 和 CyanStore