又開了一個新的坑,筆者工做以後維護着一個 NoSQL 數據庫。而筆者維護的數據庫正是基於社區版本的 Aerospike打造而來。因此這個踩坑系列的文章屬於工做總結型的內容,會將使用開發 Aerospike 的各類問題進行總結梳理,但願可以給予你們啓發和幫助。第一篇開山之文,就先從Aerospike 公司在16年數據庫頂會 VLDB的一篇論文 《Aerospike: Architecture of a Real Time Operational DBMS》展開,來高屋建瓴的審視一下 Aeropike 的設計思路,來看看如何Aerospike這款分佈式數據庫有什麼亮點值得咱們學習借鑑的,因爲論文發佈在2016年,筆者完成這篇文章時Aerospike的版本已經發布到4.5了,不少最新的實現與老論文已經有些不一樣了,這點但願你們理解。準備好,老司機發車了~~算法
從論文的題目出發,這篇文章的核心在於實時操做數據庫的架構,在論文引言之中對Aerospike的定位是一個高性能分佈式數據庫,用於處理實時的交互式在線服務。因此說,大多數使用Aerospike的場景是實時決策系統,它們有海量的數據規模,而且有嚴格的SLA要求,同時是百萬級別的 QPS,具備ms的查詢時延。顯然,這樣的場景使用傳統的 RDMS 是不現實的,在論文之中,提到 Aerospike 的一個典型的應用場景,廣告推薦系統,咱們來一塊兒看看它們是如何契合的:數據庫
衆所周知,廣告推薦系統這樣的應用場景須要極高的吞吐量、低延遲和穩定的可用性。同時,廣告推薦系統具備隨時間增長其數據使用量以提升其推薦的質量的趨勢,即,在固定時間量中可訪問的數據越多,推薦就越精確。下圖展現了一個廣告推薦系統是如何結合 Aerospike來提供推薦服務的:
緩存
顯然,這就是筆者以前的文章之中聊到的典型的Lambda架構,筆者當時正是以廣告推薦系統進行舉例的。因此在這裏筆者就不展開再聊Aerospike在其中充當的實時流存儲的角色了,感興趣的朋友能夠看這裏。服務器
除了廣告推薦系統以外,論文的原文還介紹了許多關於Aerospike的適用場景,有興趣的能夠經過原文深刻了解。接下來咱們直奔主題,來看看Aerospike的整體架構:網絡
由上圖所示,Aerospike核心分爲三個層次:架構
因此接下來咱們來一一解構,Aerospike的各個層次。分佈式
與Cassandra相似的是,Aerospike也採用了P2P的架構,也就是說,集羣之中不存在的中心節點,每一個節點都是對等的結構。而分佈式層聚焦在兩點之上:函數
節點須要處理節點成員關係,並對Aerospike集羣當前成員達成共識。好比:網絡故障和節點加入或離開。post
節點分佈所關心的點在於:性能
集羣中的全部節點到達當前集羣成員的單一一致視圖。
自動檢測新節點的加入與離開。
檢測網絡故障而且可以容忍網絡的不穩定性。
儘可能縮短集羣成員變化的時間。
每一個Aerospike節點都會自動分配一個惟一的節點標識符,它是其MAC地址和監聽端口惟一肯定的。集羣總體視圖由一個元組定義:<cluster_key,succession_list>
cluster_key標識當前集羣成員身份狀態,並在每次集羣視圖更改時更改。 它使得Aerospike節點用於區分兩個不一樣的集羣視圖。對集羣視圖的更改都對集羣的性能有着有着顯著影響,這意味着須要快速檢測節點加入/離開,而且隨後須要存在有效的一致性機制來處理對集羣視圖的更改。
節點的加入或離開是經過不一樣節點之間按期交換的心跳消息來檢測的。集羣中的每一個節點都維護一個鄰接列表,該列表是最近向節點發送心跳消息的節點列表。若是在配置的超時間隔內,因爲沒有收到對應的心跳消息,從鄰近列表中刪除對應的節點。
而節點檢測機制須要保證:
在阻塞的網絡中,有可能任意丟失某些數據包。所以,除了常規的心跳消息以外,節點還使用了按期交換的其餘消息做爲備選的輔助心跳機制。例如,副本寫能夠用做心跳消息的輔助。這確保了,只要節點之間的主要或次要心跳通訊是完整的,僅主心跳信息的丟失不會引發集羣視圖的變動。
集羣中的每一個節點能夠經過計算平均消息丟失來評估其每一個節點的健康評分,健康評分是經過:每一個節點接收的預期消息數量與每一個節點接收的實際消息數量的加權平均值計算而成的。
設t爲心跳消息的發送間隔,w爲心跳信息的發送頻率,r爲在這個窗口時間中丟失的心跳消息的數量,α是一個比例因子,la(prev)以前的健康因子。la(new)爲更新以後的健康因子,因此它的計算方式以下圖所示:
健康因子在全部節點標準差兩倍的節點是異常值,而且被認爲是不健康的。若是不健康的節點是集羣的成員,則將其從集羣中刪除。若是不是成員,則直到其平均消息丟失在可容忍的限度內才能加入集羣。在實踐中,α被設置爲0.95,節點的歷史表現比賦予了更多的權重。窗口時間通常設置爲1秒。
對鄰近列表的更改就會產生新集羣視圖,這須要一次Paxos一致性算法。鄰接鏈表之中節點標識符最高的節點充當Paxos提議者,若是建議被接受,節點就開始從新分配數據。
Aerospike實現了最小化集羣因爲單一故障事件而更改視圖的次數。例如,有故障的網絡交換機可能使集羣成員的子集不可到達。一旦恢復了網絡,就須要將這些節點添加到集羣中。若是每一個丟失或加入的節點都須要觸發建立新的集羣視圖,這種代價是很高的。因此Aerospike僅在固定的集羣更改間隔(間隔自己的時間是可配置的)開始時作出集羣視圖的調整。這裏的想法是避免如心跳子系統檢測到的那樣對節點到達和離開事件反應太快,而是用一個集羣視圖更改來處理一批節點加入或刪除的事件。這避免了由重複的集羣視圖更改和數據分佈致使的大量潛在開銷。集羣更改間隔等於節點超時值的兩倍,確保在單個間隔中明確檢測到因爲單個網絡故障而失敗的全部節點。
Aerospike使用RipeMD160算法將record的key散列爲160bit的digest,digest被劃分爲4096個分區。分區是Aerospike中最小的數據分佈單元,根據key 的digest爲記錄分配分區。即便key的分佈是傾斜的,在digest空間中分佈也是均勻的,它有助於避免在數據訪問期間建立熱點,這有助於系統的容錯。
一個好的數據分佈須要知足下列條件:
數據分配算法爲每一個分區生成一個副本列表。副本列表中的第一個節點是該分區的主節點,其他的節點是副本。在默認狀況下,全部讀/寫都經過副本的主節點。Aerospike支持任意數量的副本,(一般設置爲兩副本,筆者在實際使用中也是兩副本)。 Aerospike 採起的是一致性哈希的分片分配的方式,當節點出現失效或宕機的狀況時。這個節點能夠從副本列表中刪除,然後續節點的左移。以下圖所示,若是該節點須要承載了數據的副本,則須要將此分區中的記錄複製到新節點。一旦原始節點返回並再次成爲集羣的一部分,它將簡單地從新得到其在分區複製列表中的位置。向集羣中添加一個全新的節點將具備將此節點插入各個分區副本列表中的某個位置的效果。所以,將致使每一個分區的後續節點的右移,而新節點左側的分配不受影響。
上面的討論給出了算法就能確保副本的最低遷移成本。可是當一個節點被刪除並從新加入集羣時,它須要和其餘副本進行同步。當一個全新的節點加入一個擁有大量現有數據的集羣,因此新的節點須要得到對應分區中全部記錄的全新副本,而且還可以處理新的讀寫操做。接下來咱們來看看副本同步的機制:
將record從一個節點移動到另外一個節點的過程稱爲遷移。在每次集羣視圖改變以後,就須要進行數據遷移。每一個分區的主副本爲對應的分區分配惟一的分區版本,這個版本號會被複制到各個副本中。在集羣視圖更改以後,節點之間交換分區的分區版本和數據。
Aerospike使用增量遷移的方式優化遷移的速度。若是在可以在分區版本上創建總順序,那麼數據遷移的過程將更加有效。例如,若是節點1上的分區版本的值小於節點2上的相同分區版本的值,則節點1上的分區版本可能被丟棄。可是,經過分區版本號的排序是有問題的,由於網絡分區引發的集羣分裂會引發分區版本的衝突。
因此當兩個版本衝突時,節點須要協商實際記錄中的差別,並經過只對應於兩個分區版本之間的差別的數據發送。在某些狀況下,能夠根據分區版本順序徹底避免遷移。在其餘狀況下,如滾動升級,能夠傳遞增量的數據,而不是遷移整個分區。
節點從新啓動是很常見的場景,好比:服務升級,宕機重啓等。Aerospike的索引是內存中的而沒有存儲在持久設備上。在節點從新啓動時,須要經過掃描持久設備上的記錄來從新構建索引。(這個過程巨慢無比,筆者目前維護的大集羣,單機存儲數據量達1T,單次啓動須要30分鐘之久)
爲了不在每次從新啓動時從新構建索引,Aerospike的利用了共享內存來實現快速重啓。(目前開源的版本是不支持這個功能的,筆者所在的團隊經過二次開發實現了對應的功能。可是機器一旦重啓以後,也必須重建索引,因此有機器頻繁重啓的,能夠考慮一些對應索引進行落盤)
在Aerospike中,每一個節點維護着一個鄰接列表標識着全局的節點分佈狀況。客戶端從一個種子節點,發現整個集羣的節點。
每一個客戶端進程都將集羣分區映射的信息存儲在共享內存之中。爲了保持信息最新,客戶端進程按期經過AeroSpike節點,來檢查集羣是否有任何變更。它經過根據服務器的最新版本檢查本地存儲的版原本實現這一點。對於單機的多個客戶端,AeroSpike將數據存儲在共享內存之中,而且用跨進程的互斥代碼來實現集羣信息的共享。
####2.3.2 鏈接管理
對於每一個集羣節點,在初始化時,客戶端需爲節點建立一個內存結構,並存儲其分區映射,而且爲節點維護鏈接。一旦出現節點和客戶端的網絡問題,這種頻繁的內存調整容易產生性能問題。因此Aerospike客戶端實現如下策略:
####2.3.2.1 健康計數
爲了不因爲偶爾的網絡故障致使上文的問題。當客戶端鏈接集羣節點操做發生問題時,會對集羣節點進行故障計數。當故障計數超過特定閾值時,客戶端纔會刪除集羣節點。對集羣節點的成功操做能夠將故障計數重置爲0。
####2.3.2.2 節點諮詢
網絡的故障一般很難複雜。在某些極端狀況下,集羣節點能夠彼此感知,可是客戶端不能直接感知到集羣節點X。在這些狀況下,客戶端鏈接集羣之中全部可見節點,並諮詢集羣之中的全部節點在其鄰接列表中是否包含X。若是沒有包含,則客戶端將等待一個閾值時間,永久移除X節點。
在正常狀態下(即,當沒有故障時),每一個節點只將節點上主副本的數據傳送到遠程集羣。只在節點出現故障時才使用從副本。若是一個節點出現失效,全部其餘節點可以檢測到,並表明失效的節點接管工做。
當發生寫操做時,主副本在日誌之中記錄。進行數據傳輸時,首先讀取一批日誌,若是同一個記錄有多個更新,選取一批之中最近的更新記錄。一旦選取了記錄,將其與實際記錄比較。若是日誌文件上的記錄小於實際的記錄,則跳過該記錄。對於可是跳過記錄的次數有一個上限,由於若是記錄不斷更新,那麼可能永遠不會推送記錄。當系統中存在頻繁更新記錄的熱鍵時,這些優化提供了巨大的好處。
Aerospike的存儲層是一個混合模型,其中索引存儲在內存中(不持久),數據能夠選擇存儲在持久存儲(SSD)或內存之中。而隨機的讀寫SSD容易產生寫放大。(筆者以前的文章也一樣聊過這個問題,能夠參考這裏)爲了不在SSD的單個塊上產生不均勻的磨損,Aerospike採起了批量寫的方式。當更新記錄時,從SSD讀取舊記錄,並將更新後的副本寫入緩衝區。當緩衝區在充滿時刷新到SSD上。
讀取單元RBLOCKS的大小是128字節。而WBLOCK的大小,可配置,一般爲1MB。這樣的寫入優化了磁盤壽命。Aerospike經過Hash函數在多個設備上切分數據來操做多個設備。這容許並行訪問多個設備,同時避免任何熱點。
Aerospike經過運行後臺碎片整理進程來回收空間。每一個設備對應的塊都存在填充因子。塊的填充因子寫入在塊中。系統啓動時,存儲系統載入塊中的填充因子,並在每次寫入時保持更新。當塊的填充因子低於閾值時,塊成爲碎片整理的候選者,而後排隊等待碎片整理。
塊進行碎片整理時,將讀取有效記錄並將其移動到新的寫入緩衝區,當寫入緩衝區已滿時,將其刷新到磁盤。爲了不混合新寫和舊寫,Aerospike維護兩個不一樣的寫緩衝隊列,一個用於普通客戶端寫,另外一個用於碎片整理。
設置一個較高的閾值(一般爲50%)會致使設備不斷的刷寫。而較低的設置會下降磁盤的利用率。因此基於可當即被寫入可用磁盤空間,調整碎片整理速率以確保有效的空間利用。
Aerospike沒有維護LRU緩存,而是維護的post write queue。這是最近寫入的數據緩存,這個緩存不須要額外的內存空間。post write queue提升了緩存命中率,並減小了存儲設備上的I/O負載。
關於論文之中對Aerospike的設計筆者已經夾帶私貨的闡述清晰了。而關於單機優化和Aerospike性能測試,筆者就再也不贅述了,感興趣的能夠回到論文之中繼續一探究竟。對於論文之中的細節想要進一步的瞭解,能夠繼續關注筆者後續關於Aerospike的拆坑手記~~~