又開了一個新的坑,筆者工作之後維護著一個 NoSQL 資料庫 。而筆者維護的資料庫正是基於 社區版本的 Aerospike 打造而來。所以這個踩坑系列的文章屬於工作總結型的內容,會將使用開發 Aerospike 的各種問題進行總結梳理,希望能夠給予大家啟發和幫助。第一篇開山之文,就先從Aerospi ...
又開了一個新的坑,筆者工作之後維護著一個 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來提供推薦服務的:
顯然,這就是筆者之前的文章之中聊到的典型的Lambda架構,筆者當時正是以廣告推薦系統進行舉例的。所以在這裡筆者就不展開再聊Aerospike在其中充當的實時流存儲的角色了,感興趣的朋友可以看這裡。
2.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 採取的是一致性哈希的分片分配的方式,當節點出現失效或宕機的情況時。這個節點可以從副本列表中刪除,而後續節點的左移。如下圖所示,如果該節點需要承載了數據的副本,則需要將此分區中的記錄複製到新節點。一旦原始節點返回並再次成為集群的一部分,它將簡單地重新獲得其在分區複製列表中的位置。向集群中添加一個全新的節點將具有將此節點插入各個分區副本列表中的某個位置的效果。因此,將導致每個分區的後續節點的右移,而新節點左側的分配不受影響。
上面的討論給出了演算法就能確保副本的最低遷移成本。但是當一個節點被刪除並重新加入集群時,它需要和其他副本進行同步。當一個全新的節點加入一個擁有大量現有數據的集群,所以新的節點需要獲得對應分區中所有記錄的全新副本,並且還能夠處理新的讀寫操作。接下來我們來看看副本同步的機制:
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上。
讀取單元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的拆坑手記~~~