阿裡雲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的基本處理流程總結參考下圖所示,協定細節本文不做擴充:

對于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(37、38、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®團隊在技術積累與沉澱方面會越做越好,成為時序資料庫技術上司者。
重磅公測
阿裡雲InfluxDB®現已開啟全面公測(歡迎通路公測
購買頁面與
文檔)。公測期間免費體驗,另有釘釘答疑咨詢群如下,歡迎大家掃碼加入。