AeroSpike踩坑手記1:Architecture of a Real Time Operational DBMS論文導讀

happenlee發表於2019-01-20

又開了一個新的坑,筆者工作之後維護著一個 NoSQL 資料庫。而筆者維護的資料庫正是基於社群版本的 Aerospike打造而來。所以這個踩坑系列的文章屬於工作總結型的內容,會將使用開發 Aerospike 的各種問題進行總結梳理,希望能夠給予大家啟發和幫助。第一篇開山之文,就先從Aerospike 公司在16年資料庫頂會 VLDB的一篇論文 《Aerospike: Architecture of a Real Time Operational DBMS》展開,來高屋建瓴的審視一下 Aeropike 的設計思路,來看看如何Aerospike這款分散式資料庫有什麼亮點值得我們學習借鑑的,由於論文釋出在2016年,筆者完成這篇文章時Aerospike的版本已經發布到4.5了,很多最新的實現與老論文已經有些不同了,這點希望大家理解。準備好,老司機發車了~~

1.AeroSpike 的定位與場景

從論文的題目出發,這篇文章的核心在於實時運算元據庫的架構,在論文引言之中對Aerospike的定位是一個高效能分散式資料庫,用於處理實時的互動式線上服務。所以說,大多數使用Aerospike的場景是實時決策系統,它們有海量的資料規模,並且有嚴格的SLA要求,同時是百萬級別的 QPS,具有ms的查詢時延。顯然,這樣的場景使用傳統的 RDMS 是不現實的,在論文之中,提到 Aerospike 的一個典型的應用場景,廣告推薦系統,我們來一起看看它們是如何契合的:

眾所周知,廣告推薦系統這樣的應用場景需要極高的吞吐量、低延遲和穩定的可用性。同時,廣告推薦系統具有隨時間增加其資料使用量以提高其推薦的質量的趨勢,即,在固定時間量中可訪問的資料越多,推薦就越精確。下圖展示了一個廣告推薦系統是如何結合 Aerospike來提供推薦服務的:
利用 Aerospike 實現的廣告推薦系統,一個典型的 Lambda 架構的場景

顯然,這就是筆者之前的文章之中聊到的典型的Lambda架構,筆者當時正是以廣告推薦系統進行舉例的。所以在這裡筆者就不展開再聊Aerospike在其中充當的實時流儲存的角色了,感興趣的朋友可以看這裡

2.Aerospike的總體架構

除了廣告推薦系統之外,論文的原文還介紹了許多關於Aerospike的適用場景,有興趣的可以通過原文深入瞭解。接下來我們直奔主題,來看看Aerospike的總體架構

Aerospike的總體架構

由上圖所示,Aerospike核心分為三個層次:

  • 客戶端層
  • 分散式層
  • 資料層

所以接下來我們來一一解構,Aerospike的各個層次。

2.1 分散式層

與Cassandra類似的是,Aerospike也採用了P2P的架構,也就是說,叢集之中不存在的中心節點,每個節點都是對等的結構。而分散式層聚焦在兩點之上:

  • 節點分佈
  • 資料分佈

2.1.1 節點分佈

節點需要處理節點成員關係,並對Aerospike叢集當前成員達成共識。比如:網路故障和節點加入或離開。

節點分佈所關心的點在於:

  • 叢集中的所有節點到達當前叢集成員的單一一致檢視。

  • 自動檢測新節點的加入與離開。

  • 檢測網路故障並且能夠容忍網路的不穩定性。

  • 儘量縮短叢集成員變化的時間。

2.1.1.1 叢集檢視

每個Aerospike節點都會自動分配一個唯一的節點識別符號,它是其MAC地址和監聽埠唯一確定的。叢集整體檢視由一個元組定義:<cluster_key,succession_list>

  • cluster_key是隨機生成的8位元組值標識一個唯一的叢集檢視
  • succession_list 是一個集合,標識了所有屬於叢集的Aerospike節點

cluster_key標識當前叢集成員身份狀態,並在每次叢集檢視更改時更改。 它使得Aerospike節點用於區分兩個不同的叢集檢視。對叢集檢視的更改都對叢集的效能有著有著顯著影響,這意味著需要快速檢測節點加入/離開,並且隨後需要存在有效的一致性機制來處理對叢集檢視的更改。

2.1.1.2 節點檢測

節點的加入或離開是通過不同節點之間定期交換的心跳訊息來檢測的。叢集中的每個節點都維護一個鄰接列表,該列表是最近向節點傳送心跳訊息的節點列表。如果在配置的超時間隔內,由於沒有收到對應的心跳訊息,從鄰近列表中刪除對應的節點。

而節點檢測機制需要保證:

  • 避免由於零星和短暫的網路故障而將節點誤刪除出叢集。
  • 防止不穩定節點頻繁加入和離開叢集。
輔助心跳

在阻塞的網路中,有可能任意丟失某些資料包。因此,除了常規的心跳訊息之外,節點還使用了定期交換的其他訊息作為備選的輔助心跳機制。例如,副本寫可以用作心跳訊息的輔助。這確保了,只要節點之間的主要或次要心跳通訊是完整的,僅主心跳資訊的丟失不會引起叢集檢視的變更。

健康檢測

叢集中的每個節點可以通過計算平均訊息丟失來評估其每個節點的健康評分,健康評分是通過:每個節點接收的預期訊息數量與每個節點接收的實際訊息數量的加權平均值計算而成的。

設t為心跳訊息的傳送間隔,w為心跳資訊的傳送頻率,r為在這個視窗時間中丟失的心跳訊息的數量,α是一個比例因子,la(prev)之前的健康因子。la(new)為更新之後的健康因子,所以它的計算方式如下圖所示:
健康因子的計算

健康因子在所有節點標準差兩倍的節點是異常值,並且被認為是不健康的。如果不健康的節點是叢集的成員,則將其從叢集中刪除。如果不是成員,則直到其平均訊息丟失在可容忍的限度內才能加入叢集。在實踐中,α被設定為0.95,節點的歷史表現比賦予了更多的權重。視窗時間一般設定為1秒。

2.1.1.3 檢視更改

對鄰近列表的更改就會產生新叢集檢視,這需要一次Paxos一致性演算法。鄰接連結串列之中節點識別符號最高的節點充當Paxos提議者,如果建議被接受,節點就開始重新分配資料。

Aerospike實現了最小化叢集由於單一故障事件而更改檢視的次數。例如,有故障的網路交換機可能使叢集成員的子集不可到達。一旦恢復了網路,就需要將這些節點新增到叢集中。如果每個丟失或加入的節點都需要觸發建立新的叢集檢視,這種代價是很高的。所以Aerospike僅在固定的叢集更改間隔(間隔本身的時間是可配置的)開始時做出叢集檢視的調整。這裡的想法是避免如心跳子系統檢測到的那樣對節點到達和離開事件反應太快,而是用一個叢集檢視更改來處理一批節點加入或刪除的事件。這避免了由重複的叢集檢視更改和資料分佈導致的大量潛在開銷。叢集更改間隔等於節點超時值的兩倍,確保在單個間隔中明確檢測到由於單個網路故障而失敗的所有節點。

2.2 資料分佈

Aerospike使用RipeMD160演算法將record的key雜湊為160bit的digest,digest被劃分為4096個分割槽。分割槽是Aerospike中最小的資料分佈單元,根據key 的digest為記錄分配分割槽。即使key的分佈是傾斜的,在digest空間中分佈也是均勻的,它有助於避免在資料訪問期間建立熱點,這有助於系統的容錯。
一個好的資料分佈需要滿足下列條件:

  • 儲存負載均勻地分佈在叢集中,
  • 具有較好的擴充套件性
  • 節點出現變化時,資料的重新平衡是非破壞性的

Aerospike 的資料分佈

資料分配演算法為每個分割槽生成一個副本列表。副本列表中的第一個節點是該分割槽的主節點,其餘的節點是副本。在預設情況下,所有讀/寫都通過副本的主節點。Aerospike支援任意數量的副本,(通常設定為兩副本,筆者在實際使用中也是兩副本)。 Aerospike 採取的是一致性雜湊的分片分配的方式,當節點出現失效或當機的情況時。這個節點可以從副本列表中刪除,而後續節點的左移。如下圖所示,如果該節點需要承載了資料的副本,則需要將此分割槽中的記錄複製到新節點。一旦原始節點返回並再次成為叢集的一部分,它將簡單地重新獲得其在分割槽複製列表中的位置。向叢集中新增一個全新的節點將具有將此節點插入各個分割槽副本列表中的某個位置的效果。因此,將導致每個分割槽的後續節點的右移,而新節點左側的分配不受影響。

主副本的分佈與副本的移動

上面的討論給出了演算法就能確保副本的最低遷移成本。但是當一個節點被刪除並重新加入叢集時,它需要和其他副本進行同步。當一個全新的節點加入一個擁有大量現有資料的叢集,所以新的節點需要獲得對應分割槽中所有記錄的全新副本,並且還能夠處理新的讀寫操作。接下來我們來看看副本同步的機制:

2.2.1 資料遷移

將record從一個節點移動到另一個節點的過程稱為遷移。在每次叢集檢視改變之後,就需要進行資料遷移。每個分割槽的主副本為對應的分割槽分配唯一的分割槽版本,這個版本號會被複制到各個副本中。在叢集檢視更改之後,節點之間交換分割槽的分割槽版本和資料。

2.2.1.1 增量遷移

Aerospike使用增量遷移的方式優化遷移的速度。如果在能夠在分割槽版本上建立總順序,那麼資料遷移的過程將更加有效。例如,如果節點1上的分割槽版本的值小於節點2上的相同分割槽版本的值,則節點1上的分割槽版本可能被丟棄。但是,通過分割槽版本號的排序是有問題的,因為網路分割槽引起的叢集分裂會引起分割槽版本的衝突。
所以當兩個版本衝突時,節點需要協商實際記錄中的差異,並通過只對應於兩個分割槽版本之間的差異的資料傳送。在某些情況下,可以根據分割槽版本順序完全避免遷移。在其他情況下,如滾動升級,可以傳遞增量的資料,而不是遷移整個分割槽。

  • 遷移流程中的讀寫
    如果分割槽正在進行遷移時,如果此時對應的分割槽有讀寫,主副本會讀取所有的分割槽版本,協調出一個最終勝出的版本用於讀或寫事務。(按照筆者對文章的理解,這個流程會涉及多個副本,是一個耗時的操作
  • 沒有資料的主副本
    新新增到正在執行的叢集的空節點成為了主副本,並且沒有對應分割槽的資料,沒有任何資料的分割槽的副本被標記為處於DESYNC狀態。Aerospike會指定一個最多記錄的分割槽版本作為這個分割槽的代理主副本。所有的讀操作都會指向代理主副本。(此時寫還是在主副本上如果客戶端可以容忍讀取舊版本的記錄,則可以減少協調勝出版本的損耗。此代理主副本的工作會持續到對應分割槽的遷移完成。
  • 遷移順序
    • 小分割槽優先
      讓分割槽版本中記錄最少的分割槽開始遷移。這種策略可以快速減少特定分割槽的不同副本的數量。隨著遷移的完成,延遲會改善,需要進行協調副本版本會減少對應的節點進行的通訊。
    • 熱分割槽優先
      根據分割槽的 qps 的大小確認分割槽遷移的順序。這種策略的目標與小分割槽優先的邏輯是一致的。

2.2.2 快速重啟

節點重新啟動是很常見的場景,比如:服務升級,當機重啟等。Aerospike的索引是記憶體中的而沒有儲存在持久裝置上。在節點重新啟動時,需要通過掃描持久裝置上的記錄來重新構建索引。(這個過程巨慢無比,筆者目前維護的大叢集,單機儲存資料量達1T,單次啟動需要30分鐘之久

為了避免在每次重新啟動時重新構建索引,Aerospike的利用了共享記憶體來實現快速重啟。(目前開源的版本是不支援這個功能的,筆者所在的團隊通過二次開發實現了對應的功能。但是機器一旦重啟之後,也必須重建索引,所以有機器頻繁重啟的,可以考慮一些對應索引進行落盤

2.3 客戶端層

2.3.1 服務發現

在Aerospike中,每個節點維護著一個鄰接列表標識著全域性的節點分佈情況。客戶端從一個種子節點,發現整個叢集的節點。
每個客戶端程式都將叢集分割槽對映的資訊儲存在共享記憶體之中。為了保持資訊最新,客戶端程式定期通過AeroSpike節點,來檢查叢集是否有任何變動。它通過根據伺服器的最新版本檢查本地儲存的版本來實現這一點。對於單機的多個客戶端,AeroSpike將資料儲存在共享記憶體之中,並且用跨程式的互斥程式碼來實現叢集資訊的共享。
####2.3.2 連線管理
對於每個叢集節點,在初始化時,客戶端需為節點建立一個記憶體結構,並儲存其分割槽對映,並且為節點維護連線。一旦出現節點和客戶端的網路問題,這種頻繁的記憶體調整容易產生效能問題。所以Aerospike客戶端實現以下策略:
####2.3.2.1 健康計數
為了避免由於偶爾的網路故障導致上文的問題。當客戶端連線叢集節點操作發生問題時,會對叢集節點進行故障計數。當故障計數超過特定閾值時,客戶端才會刪除叢集節點。對叢集節點的成功操作可以將故障計數重置為0。
####2.3.2.2 節點諮詢
網路的故障通常很難複雜。在某些極端情況下,叢集節點可以彼此感知,但是客戶端不能直接感知到叢集節點X。在這些情況下,客戶端連線叢集之中所有可見節點,並諮詢叢集之中的所有節點在其鄰接列表中是否包含X。如果沒有包含,則客戶端將等待一個閾值時間,永久移除X節點。

3 跨資料中心同步

3.1.1 失效接管

在正常狀態下(即,當沒有故障時),每個節點只將節點上主副本的資料傳送到遠端叢集。只在節點出現故障時才使用從副本。如果一個節點出現失效,所有其他節點能夠檢測到,並代表失效的節點接管工作。

3.1.2 資料傳輸優化

當發生寫操作時,主副本在日誌之中記錄。進行資料傳輸時,首先讀取一批日誌,如果同一個記錄有多個更新,選取一批之中最近的更新記錄。一旦選取了記錄,將其與實際記錄比較。如果日誌檔案上的記錄小於實際的記錄,則跳過該記錄。對於但是跳過記錄的次數有一個上限,因為如果記錄不斷更新,那麼可能永遠不會推送記錄。當系統中存在頻繁更新記錄的熱鍵時,這些優化提供了巨大的好處。

4 儲存落地

4.1 儲存管理

Aerospike的儲存層是一個混合模型,其中索引儲存在記憶體中(不持久),資料可以選擇儲存在持久儲存(SSD)或記憶體之中。而隨機的讀寫SSD容易產生寫放大。(筆者之前的文章也同樣聊過這個問題,可以參考這裡)為了避免在SSD的單個塊上產生不均勻的磨損,Aerospike採取了批量寫的方式。當更新記錄時,從SSD讀取舊記錄,並將更新後的副本寫入緩衝區。當緩衝區在充滿時重新整理到SSD上。
Aerospike儲存層
讀取單元RBLOCKS的大小是128位元組。而WBLOCK的大小,可配置,通常為1MB。這樣的寫入優化了磁碟壽命。Aerospike通過Hash函式在多個裝置上切分資料來操作多個裝置。這允許並行訪問多個裝置,同時避免任何熱點。

4.2 Defragmentation垃圾清理

Aerospike通過執行後臺碎片整理程式來回收空間。每個裝置對應的塊都存在填充因子。塊的填充因子寫入在塊中。系統啟動時,儲存系統載入塊中的填充因子,並在每次寫入時保持更新。當塊的填充因子低於閾值時,塊成為碎片整理的候選者,然後排隊等待碎片整理。
塊進行碎片整理時,將讀取有效記錄並將其移動到新的寫入緩衝區,當寫入緩衝區已滿時,將其重新整理到磁碟。為了避免混合新寫和舊寫,Aerospike維護兩個不同的寫緩衝佇列,一個用於普通客戶端寫,另一個用於碎片整理。
設定一個較高的閾值(通常為50%)會導致裝置不斷的刷寫。而較低的設定會降低磁碟的利用率。所以基於可立即被寫入可用磁碟空間,調整碎片整理速率以確保有效的空間利用。

4.3 效能與調優

4.3.1 Post Write Queue

Aerospike沒有維護LRU快取,而是維護的post write queue。這是最近寫入的資料快取,這個快取不需要額外的記憶體空間。post write queue提高了快取命中率,並減少了儲存裝置上的I/O負載。

5.小結

關於論文之中對Aerospike的設計筆者已經夾帶私貨的闡述清晰了。而關於單機優化和Aerospike效能測試,筆者就不再贅述了,感興趣的可以回到論文之中繼續一探究竟。對於論文之中的細節想要進一步的瞭解,可以繼續關注筆者後續關於Aerospike的拆坑手記~~~

相關文章