Categories
程式開發

海量非結構化數據存儲中的小對象合併技術


隨著人工智能, IoT 等技術的推廣普及,智能監控,智能製造等新興領域蓬勃發展,湧現出了越來越多的海量非結構化數據存儲需求。舉例來說,服務於公安機關的智能監控程序,不僅需要存儲視頻,而且還需要存儲因使用人臉識別技術而產生的人臉截圖等文件。這一類的小圖像通常只有幾個 KiB 到 幾十個 KiB 大小,但是數目巨大,動輒十億甚至百億規模。

這類業務,並不需要文件系統提供的複雜語義。相反,只需要存取(PUT / GET)的簡單操作模式,使得這類業務非常適合使用對象存儲。雖然對象存儲相比文件存儲更擅長處理數目巨大的非結構化數據,但對於十億甚至百億的規模,依然會對現有的系統(如軟件定義存儲中最常用的Ceph)造成嚴重的性能和可用性的衝擊。本文將介紹如何通過引入adCache、PhxKV等自研組件,成功支持百億級別的海量小對象存儲。

基於Ceph方案處理海量小對象的問題

為了說明 Ceph 處理小對象合併的問題,我們首先簡要回顧 RADOS Gateway ( RGW, Ceph 的對象存儲接口)的工作原理。 RGW 工作在 Ceph 的底層存儲 RADOS 之上。 RADOS 對外提供存取 RADOS Object (為了區分對象存儲,我們把 RADOS Object 稱為 R-obj )的接口。一個 R-obj 除去數據之外,還可以維護一定數量的 KV 形式表示的元數據( omap )。 RGW 就是利用RADOS 的接口,構建了對象存儲。其中,一個對象,會有三類與之相關的 R-obj。

海量非結構化數據存儲中的小對象合併技術 1

如上圖所示,對​​象存儲中的每個桶,都會有一個對應的bucket index R-obj,桶中的每一個對象,都會對應該R-obj 的一條omap,存儲諸如創建時間,權限信息等系統元數據。由於 R-obj 大小的限制,對象的數據和用戶自定義元數據部分,被切割之後存儲在一個 header R-obj 和若干個 tail R-obj 中。 Bucket index R-obj 肩負著索引功能,以及沖突處理的邏輯,所以,每個寫操作都需要操作 bucket index R-obj。

Ceph 底層存儲 RADOS 的一致性協議,為其處理海量小對象帶來了很大的問題。 RADOS 在維護複製組 ( PG , Placement Group )的一致性時,要求復制組內所有在線(狀態為 up )的 OSD 都返回成功,一個 op 才算完成。而 OSD 的狀態,是由 monitor 監控 OSD 超時,更新視圖並擴散至全集群的。

海量非結構化數據存儲中的小對象合併技術 2

在上圖的例子中,OP2 在處理過程中,從 OSD2 發生離線故障,這時,由於從 OSD2 仍然被認為在線,導致 OP2 被掛起,直到 monitor 更新從 OSD2 的狀態,OP2 才可以被返回。當OSD2 從離線狀態重新上線時,會執行修復操作,同步自己和主 OSD 的狀態。

為了修復複製組中離線的 OSD ,重新上線的 OSD 會比對自身和主 OSD 中的 op log ,定位出離線期間發生修改的 R-obj ,並從主 OSD 中復制這些 R-obj 的副本。注意,RADOS 中 op log 的作用只為定位發生改變的 R-obj ,並不能通過 replay op log 的方式進行數據恢復。所以,發生變化的 R-obj 都需要進行全量修復。即使bucket index R-obj 只是在從OSD2 離線時插入了一條omap,修復bucket index R-obj 的過程中,依然需要復制整個bucket index R-obj 的omap 列表,且修復過程也會阻塞業務I/O 。

講到此處,我們已經不難看出 RGW 處理海量小對象時,元數據處理帶來的問題:

  1. OSD 短暫離線,造成 bucket index R-obj 無法訪問時,會阻塞業務 I/O。

  2. 如果離線時間超過了心跳超時,觸發視圖變更,業務恢復,但是會導致 OSD 重新上線時全量修復 bucket index R-obj。海量小對象場景下,bucket index R-obj 擁有數量巨大的 omap,修復耗時,進一步增加掛起請求的時間。當網絡不穩定,視圖變更頻繁時,不僅請求掛起,還會產生巨大的不必要修復流量,消耗系統資源。

使用 PhxKV 承載海量小對象

我們已經說明了RADOS 的一致性協議和修復機制在海量小對象場景中帶來的問題,接下來,我們將說明深信服企業級分佈式存儲EDS是如何通過自研的分佈式KV —— PhxKV 來支撐海量小對象的元數據存儲。我們首先介紹區分於 RADOS 一致性協議的 RAFT 協議,之後介紹我們如何以 RAFT 協議為基礎,構建了 PhxKV 分佈式 KV 系統。

RAFT 一致性協議

RAFT 協議有三個重要組成部分:Log,狀態機,和一致性模塊。一個 RAFT 組中有多個 peer,其中一個為 leader,其他為 followers。

海量非結構化數據存儲中的小對象合併技術 3

如上圖所示,客戶端將op 發送至leader 的一致性模塊,之後leader 請求所有的peer 將該op append 至Log 中,當大部分(例如,三個peer 中的兩個)append 成功時,就可以認為op 已經commit,這時,leader 更新狀態機,並返回請求。至於 followers , leader 會在後續的 op 中指示其將已經 commit 的 op log 執行,更新狀態機。

當 leader 發生故障,或者 leader 所在分片中的 peer 數少於大部分 peer 數導致 leader
下台時,RAFT 協議通過各 peer 間心跳的超時來觸發選主流程,從而進行視圖的變更。

RAFT 協議的具體細節和故障處理方面比較複雜,我們就不在此贅述,有興趣的讀者可以移步去閱讀論文 In Search of an Understandable Consensus Algorithm 。我們在此歸納 RAFT 協議的以下特點:

  1. RAFT 協議中,一個op 成功的條件更容易被滿足:當複制組中大部分節點返回成功時,一個op 即被認為處理成功,這一特徵,使得某些節點網絡不穩定或者主機重啟時,I /O 能夠不被阻塞;

  2. RAFT 協議中的視圖變更,不需要額外的 monitor 進程來觸發,而是通過複製組內各 peer 心跳超時觸發的選主操作來進行,視圖變更更加快速。觸發視圖變更的條件更苛刻(單個 follower 離線不會導致視圖變更),即使極端情況下,也能在較短時間內選出新主,快速恢復業務;

  3. RAFT 能夠通過複製和 replay log 來實現不同 peer 間 Log 和狀態機的同步。例如,一個重新上線的peer 執行的最新op 為op10000,此時,leader 執行到op10003,為了同步狀態,該peer 只需從leader 處複製op10001 ~ op10003 的log,並在本地執行,即可和leader 同步狀態機。增量修復和全量修復相比,可以大幅減少修復時間,並節省大量的網絡和 I/O 資源。

  4. 承接第一點和第三點,peer 修復的過程對上層透明,並不會阻塞業務 I/O。

PhxKV 架構

接下來,我們介紹如何基於 RAFT 協議構建 PhxKV 系統。

海量非結構化數據存儲中的小對象合併技術 4

PhxKV 的架構如上圖所示,PhxKV 提供和本地 KV 引擎類似的增刪改查和批量操作的同步異步接口。 PhxKV 的key 空間被一致性哈希映射到若干個 region 中,各 region 管理的 key 沒有重合,每個 region 對應一個 RAFT 複製組,通過 RAFT 協議維護一致性。 PhxKV 採用 RocksDB 作為底層引擎,提供本地的 KV 接口。

PhxKV 的主要組件和角色如下所述:

  1. KV agent 是 PhxKV 的服務端進程,管理存儲在相同物理介質上的 regions 。出於對故障域的考慮,不同 KV agent 管理的物理空間處在不同的 SSD 上。相同 region 的不同副本存儲在不同 KV agent 上,並通過 RAFT 協議進行同步。

  2. Metadata server 管理元數據。通過 KV agent 定時的心跳上報收集 KV agent 和 region 的健康狀況,及時通過心跳回復下達負載均衡和修復操作。出於容錯考慮,metadata server 也有多個進程運行在不同主機上,並通過 RAFT 協議進行同步。

  3. 客戶端執行 I/O 操作。客戶端通過被動定時心跳和主動心跳從 metadata server 拉取各 Region 的路由信息​​,之後,根據路由從對應的 KV Agent 中執行對應的增刪改查操作。

PhxKV 針對業務特徵,還進行了一系列深度優化:

  1. RocksDB 為了提供高性能和高可靠性的寫入操作,在寫操作時,op log 先順序寫入WAL( write-ahead log )進行持久化,數據部分則只是在內存中更新memtable,後續才以大塊I/O 的形式刷入底層SST 文件。
    事實上,RAFT 協議中的 Log 可以作為狀態機的 WAL 使用。我們通過 RocksDB 的 disable WAL 配置項關閉了狀態機的 WAL,這樣,狀態機的更新只需操作內存,減少了一半的落盤操作,大大降低了時延。不過作為代價,在掉電的情況下重啟,狀態機會發生數據丟失,我們首先需要利用 RAFT Log 來扮演 WAL,將狀態機恢復至重啟前的狀態。

  2. PhxKV 在進行修復和擴容時,需要將 Region 內的全部數據進行複制和遷移。逐條複製性能較差,我們使用 RockDB 提供的 sst_file_writer 和 IngestExternalFiles 功能,進行整 Region 的批量插入,降低了逐條插入的鎖操作對性能的影響,並消除了一致性隱患。

  3. RocksDB 的刪除操作是異步操作,並不能快速回收空間。當某個 KV agent 容量告急,遷移出 region 時,可能很久之後才會見效,影響負載均衡效果。我們深度訂製了 RocksDB,通過改變數據組織形式,使得 Region 的刪除操作可以同步快速回收空間。

PhxKV 在 EDS 海量小對像中的角色

除去 RADOS 一致性和修復帶來的問題,元數據規模過大,底層數據的碎片化也是 RGW 面對小對象合併時的棘手問題。 EDS 使用另一組件,分佈式緩存(adCache)和 PhxKV 雙劍合璧,一起打造了海量小對象的解決方案。 AdCache 將數據暫時緩存在 SSD 盤上,後續再批量回刷至 RADOS,大幅降低了寫請求的訪問時延。

海量非結構化數據存儲中的小對象合併技術 5

上圖表示了小對象合併架構中的各組件關係。在 Ceph 的原始實現中,RGW 將對象的數據和元數據都直接存儲在 RADOS 中,其中元數據以鍵值的形式存儲在 R-obj 的omap 中。 RGW 使用 librados 和 RADOS 進行交互。

為了實現上述改造邏輯,我們劫持了 RGW 對數據部分和元數據部分的操作。其中,元數據部分被重定向至 PhxKV 中。此外,數據操作被重定向至 adCache,adCache 用異步合併回刷的原則,後續對存儲的小對像被進一步聚合後,回刷至底層引擎 RADOS 中。同時,adCache 會生成二級索引,使得可以定位到小對像在合併後存儲的位置和偏移,在數據回刷至 RADOS 時,二級索引也會作為元數據被批量寫入到 PhxKV 中。

在採用了上述針對性的優化後,EDS 的小對象寫入性能,相比原生系統有了數量級的飛躍。小對象合併引入的二級索引雖然增加了讀取時的訪問路徑,但是由於減少了元數據的規模,也變相提升了 RADOS 的訪問性能,因此,整體的讀取性能並沒有下降。由於 RAFT 協議更健壯的修復機制,修復時間相比原生系統有成數量級的下降,而且能夠保持業務不中斷,修復過程中幾乎不出現業務性能的下降。

結論

海量小對象場景,是對象存儲的新機會,也為現有架構提出了新的挑戰。深信服 EDS 基於現有的架構,取長補短,合理引用新組件解決關鍵核心問題,並能夠讓老組件 RADOS 繼續發揮特長,為海量小對象的存儲打開了新思路。

作者介紹:

Eddison,從事分佈式 KV 數據庫 PhxKV 的研發工作。香港中文大學博士,研究大數據存儲系統的性能和可靠性,在 USENIX FAST, USENIX ATC 等頂級會議發表多篇學術論文。從業以來,專注分佈式一致性, KV 引擎,糾刪碼等領域,2018 年加入深信服科技。