阿里雲InfluxDB® Raft HybridStorage實現方案

背景

阿里雲InfluxDB®是阿里雲基於開源版InfluxDB打造的一款時序數據庫產品,提供更穩定的持續運行狀態、更豐富強大的時序數據計算能力。在現有的單節點版本以外,阿里雲InfluxDB®團隊還將推出多節點的高可用版本。數據庫

咱們知道現有的開源版InfluxDB只提供單節點的能力,早期開源的集羣版本功能不完善、且社區再也不提供更新與支持。通過對官網商業版InfluxDB現有文檔的研究,咱們猜想在商業版InfluxDB集羣方案中,meta信息集羣是基於一致性協議Raft作同步的,而數據是異步複製的。這種分離的方式雖然有優勢,但也引發了一系列的一致性問題,在一些公開的文檔中,官方也認可這種數據複製方案並不使人滿意。緩存

所以,團隊在參考多項技術選型後,決定採用最爲普遍使用並有較長曆史積累的ETCD/Raft做爲核心組件實現阿里雲InfluxDB®的Raft內核,對用戶全部的寫入或一致性讀請求直接進行Raft同步(不作meta信息同步與數據寫入在一致性過程當中的拆分),保證多節點高可用版本擁有知足強一致性要求的能力。數據結構

有幸筆者參與到多節點的高可用版本的開發中,期間遇到很是多的挑戰與困難。其中一項挑戰是ETCD的Raft框架移植過程當中,在移除了ETCD自身較爲複雜、對時序數據庫沒有太多做用的Raft日誌模塊後,所帶來的一系列問題。本文就業界Raft日誌的幾種不一樣實現方案作討論,並提出一種自研的Raft HybridStorage方案。架構

業內方案

ETCD

因爲咱們採用了ETCD/Raft的方案,繞不開討論一下ETCD本家的Raft日誌實現方式。併發

官網對Raft的基本處理流程總結參考下圖所示,協議細節本文不作擴展:app

對於ETCD的Raft日誌,主要包含兩個主要部分:文件部分(WAL)、內存存儲部分(MemoryStorage)。框架

文件部分(WAL),是ETCD Raft過程所用的日誌文件。Raft過程當中收到的日誌條目,都會記錄在WAL日誌文件中。該文件只會追加,不會重寫和覆蓋。異步

內存存儲部分(MemoryStorage),主要用於存儲Raft過程用到的日誌條目一段較新的日誌,可能包含一部分已共識的日誌和一些還沒有共識的日誌條目。因爲是內存維護,能夠靈活的重寫替換。MemoryStorage有兩種方式清理釋放內存:第一種是compact操做,對appliedId以前的日誌進行清理,釋放內存;第二種是週期snapshot操做,該操做會建立snapshot那一時刻的ETCD全局數據狀態並持久化,同時清理內存中的日誌。分佈式

在最新的ETCD 3.3代碼倉庫中,ETCD已經將Raft日誌文件部分(WAL)和Raft日誌內存存儲部分(MemoryStorage)都抽象提高到了與Raft節點(Node)、Raft節點id以及Raft集羣其餘節點信息(*membership.RaftCluster)平級的Server層級,這與老版本的ETCD代碼架構有較大區別,在老版本中Raft WAL與MemoryStorage都僅僅只是Raft節點(Node)的成員變量。高併發

通常狀況下,一條Raft日誌的文件部分與內存存儲部分配合產生做用,寫入時先寫進WAL,保證持久化;隨之立刻追加到MemoryStorage中,保證熱數據的高效讀取。

不管是文件部分仍是內存存儲部分,其存儲的主要數據結構一致,都是raftpb.Entry。一條log Entry主要包含如下幾個信息:

參數 描述
Term leader的任期號
Index 當前日誌索引
Type 日誌類型
Data 日誌內容

此外,ETCD Raft日誌的文件部分(WAL)還會存儲針對ETCD設計的一些額外信息,好比日誌類型、checksum等等。

CockroachDB

CockroachDB是一款開源的分佈式數據庫,具備NoSQL對海量數據的存儲管理能力,又保持了傳統數據庫支持的ACID和SQL等,還支持跨地域、去中 心、高併發、多副本強一致和高可用等特性。

CockroachDB的一致性機制也是基於Raft協議:單個Range的多個副本經過Raft協議進行數據同步。Raft協議將全部的請求以Raft Log的形式串行化並由Leader同步給Follower,當絕大多數副本寫Raft Log成功後,該Raft Log會標記爲Committed狀態,並Apply到狀態機。

咱們來分析一下CockroachDB Raft機制的關鍵代碼,能夠很明顯的觀察到也是從鼻祖ETCD的Raft框架移植而來。可是CockroachDB刪除了ETCD Raft日誌的文件存儲部分,將Raft日誌所有寫入RocksDB,同時自研一套熱數據緩存(raftentry.Cache),利用raftentry.Cache與RocksDB自身的讀寫能力(包括RocksDB的讀緩存)來保證對日誌的讀寫性能。

此外,Raft流程中的建立snapshot操做也是直接保存到RocksDB。這樣實現的緣由,我的推測是可能因爲CockroachDB底層數據存儲使用的就是RocksDB,直接使用RocksDB的能力讀寫WAL或者存取snapshot相對簡單,不須要再額外開發適用於CockroachDB特性的Raft日誌模塊了。

自研HybridStorage

移除snapshot

在阿里雲InfluxDB多節點高可用方案實現過程當中,咱們採用了ETCD/Raft做爲核心組件,根據移植過程當中的探索與InfluxDB實際須要,移除了原生的snapshot過程。同時放棄原生的日誌文件部分WAL,而改用自研方案。

爲何移除snapshot呢?原來在Raft的流程中,爲了防止Raft日誌的無限增長,會每隔一段時間作snapshot,早於snapshot index的Raft日誌請求,將直接用snapshot迴應。然而咱們的單Raft環架構若是要作snapshot,就是對整個InfluxDB作,將很是消耗資源和影響性能,並且過程當中要鎖死整個InfluxDB,這都是不能讓人接受的。因此咱們暫時不啓用snapshot功能,而是存儲固定數量的、較多的Raft日誌文件備用。

自研的Raft日誌文件模塊會週期清理最先的日誌防止磁盤開銷過大,當某個節點下線的時間並不過長時,其餘正常節點上存儲的日誌文件若是充足,則足夠知足它追取落後的數據。但若是真的發生單節點宕機太長,正常節點的日誌文件已出現被清理而不足故障節點追取數據時,咱們將利用InfluxDB的backup和restore工具,將落後節點還原至被Raft日誌涵蓋的較新的狀態,而後再作追取。

在咱們的場景下,ETCD自身的WAL模塊並不適用於InfluxDB。ETCD的WAL是純追加模式的,當故障恢復時,正常節點要相應落後節點的日誌請求時,就有必要分析並提取出相同index且不一樣term中那條最新的日誌,同時InfluxDB的一條entry可能包含超過20M的時序數據,這對於非kv模式的時序數據庫而言是很是大的磁盤開銷。

HybridStorage設計

咱們自研的Raft日誌模塊命名爲HybridStorage,即意爲內存與文件混合存取,內存保留最新熱數據,文件保證所有日誌落盤,內存、文件追加操做高度一致。

HybridStorage的設計思路是這樣的:

(1)保留MemoryStorage:爲了保持熱數據的讀取效率,內存中的MemoryStorage會保留做爲熱數據cache提高性能,可是週期清理其中最先的數據,防止內存消耗過大。

(2)從新設計WAL:WAL再也不是像ETCD那樣的純追加模式、也不須要引入相似RocksDB這樣重的讀寫引擎。新增的日誌在MemoryStorage與WAL都會保存,WAL文件中最新內容始終與MemoryStorage保持徹底一致。

通常狀況下,HybridStorage新增不一樣index的日誌條目時,須要在寫內存日誌時同時操做文件執行相似的增減。正常寫入流程以下圖所示:

當出現了同index不一樣term的日誌條目的狀況,此時執行truncate操做,截斷對應文件位置以後一直到文件尾部的所有日誌,而後從新用append方式寫入最新term編號的日誌,操做邏輯上十分清晰,不存在Update文件中間的某個位置的操做。

例如在一組Raft日誌執行append操做時,出現了以下圖所示的同index(3七、3八、39)不一樣term的日誌條目的狀況。在MemoryStorage的處理方式是:找到對應index位置的內存位置(內存位置37),並拋棄從位置A之後的所有舊日誌佔用的內存數據(由於在Raft機制中,這種狀況下內存位置37之後的那些舊日誌都是無效的,無需保留),而後拼接上本次append操做的所有新日誌。在自研WAL也須要執行相似的操做,找到WAL文件中對應index的位置(文件位置37),刪除從文件位置37以後的全部文件內容,並寫入最新的日誌。以下圖分析:

方案對比

ETCD的方案,Raft日誌有2個部分,文件與內存,文件部分由於只有追加模式,所以並非每一條日誌都是有效的,當出現同index不一樣term的日誌條目時,只有最新的term以後的日誌是生效的。配合snapshot機制,很是適合ETCD這樣的kv存儲系統。但對於InfluxDB高可用版本而言,snapshot將很是消耗資源和影響性能,並且過程當中要鎖死整個InfluxDB。同時,一次Raft流程的一條entry可能包含超過20M的時序數據。因此這種方案不適合。

CockroachDB的方案,看似偷懶使用了RocksDB的能力,但因其底層存儲引擎也是RocksDB,因此無何厚非。但對於咱們這樣須要Raft一致性協議的時序數據庫而言,引入RocksDB未免太重了。

自研的Raft HybridStorage是比較符合阿里雲InfluxDB®的場景的,自己模塊設計輕便簡介,內存保留了熱數據緩存,文件使用接近ETCD append only的方式,遇到同index不一樣term的日誌條目時執行truncate操做,刪除冗餘與無效數據,下降了磁盤壓力。

總結

本文對比了業內常見的兩種Raft日誌的實現方案,也展現了阿里雲InfluxDB®團隊自研的HybridStorage方案。在後續開發過程當中,團隊內還會對自研Raft HybridStorage進行多項優化,例如異步寫、日誌文件索引、讀取邏輯優化等等。也歡迎讀者提出本身的解決方案。相信阿里雲InfluxDB®團隊在技術積累與沉澱方面會越作越好,成爲時序數據庫技術領導者。



本文做者:德施

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索