Jepsen 測試框架在圖數據庫 Nebula Graph 中的實踐

產品細節

在本篇文章中主要介紹圖數據庫 Nebula Graph 在 Jepsen 這塊的實踐。html

Jepsen 簡介

Jepsen 是一款用於系統測試的開源軟件庫,致力於提升分佈式數據庫、隊列、共識系統等的安全性。做者 Kyle Kingsbury 使用函數式編程語言 Clojure 編寫了這款測試框架,並對多個著名的分佈式系統和數據庫進行了一致性測試。目前 Jepsen 仍在 GitHub 保持活躍,可否經過 Jepsen 的測試已經成爲各個分佈式數據庫對自身檢驗的一個標杆。node

Jepsen 的測試流程

流程圖

Jepsen 測試推薦使用 Docker 搭建集羣。默認狀況下由 6 個 container 組成,其中一個是控制節點(control node),另外 5 個是數據庫的節點(默認爲 n1-n5)。控制節點在測試程序開始後會啓用多個 worker 進程,併發地經過 SSH 登入數據庫節點進行讀寫操做。git

測試開始後,控制節點會建立一組進程,進程包含了待測試分佈式系統的客戶端。另外一個 Generator 進程產生每一個客戶端執行的操做,並將操做應用於待測試的分佈式系統。每一個操做的開始和結束以及操做結果記錄在歷史記錄中。同時,一個特殊進程 Nemesis 將故障引入系統。github

測試結束後,Checker 分析歷史記錄是否正確,是否符合一致性。用戶可使用 Jepsen 的 knossos 中提供的驗證模型,也能夠本身定義符合需求的模型對測試結果進行驗證。同時,還能夠在測試中注入錯誤對集羣進行干擾測試。shell

最後根據本次測試所規定的驗證模型對結果進行分析。數據庫

如何使用 Jepsen

使用 Jepsen 過程當中可能會遇到一些問題,能夠參考一下使用 Tips:編程

  1. 在 Jepsen 框架中,用戶須要在 DB 接口中對本身的數據庫定義下載,安裝,啓動與終止操做。在終止後,能夠將 log 文件清除,同時也能夠指定 log 的存儲位置,Jepsen 會將其拷貝至 Jepsen 的 log 文件夾中,以便連同 Jepsen 自身的 log 進行分析。
  2. 用戶還須要提供訪問本身數據庫的客戶端,這個客戶端能夠是你用 Clojure 實現的,好比 etcd 的verschlimmbesserung,也能夠是 JDBC,等等。而後須要定義 Client 接口,告訴 Jepsen 如何對你的數據庫進行操做。
  3. 在 Checker 中,你能夠選擇須要的測試模型,好比,性能測試(checker/perf)將會生成 latency 和整個測試過程的圖表,時間軸(timeline/html)會生成一個記錄着全部操做時間軸的 html 頁面。
  4. 另一個不可或缺的組件就是在 nemesis 中注入想要測試的錯誤了。網絡分區(nemesis/partition-random-halves)和殺掉數據節點(kill-node)是比較常見的注入錯誤。
  5. 在 Generator 中,用戶能夠告知 worker 進程須要生成哪些操做,每一次操做的時間間隔,每一次錯誤注入的時間間隔等等。

用 Jepsen 測試圖數據庫 Nebula Graph

分佈式圖數據庫 Nebula Graph 主要由 3 部分組成,分別是 meta 層,graph 層和 storage 層。安全

architecture

咱們在使用 Jepsen 對 kv 存儲接口進行的測試中,搭建了一個由 8 個 container 組成的集羣:一個 Jepsen 的控制節點,一個 meta 節點,一個 graph 節點,和 5 個 storage 節點,集羣由 Docker-compose 啓動。須要注意的是,要創建一個集羣的 subnet 網絡,使集羣能夠連通,另外要安裝 ssh 服務,併爲 control node 與 5 個 storage 節點配置免密登入。微信

測試中使用了 Java 編寫的客戶端程序,生成 jar 包並加入到 Clojure 程序依賴,來對 DB 進行 put,get 和 cas (compare-and-set) 操做。另外 Nebula Graph 的客戶端有自動重試邏輯,當遇到錯誤致使操做失敗時,客戶端會啓用適當的重試機制以盡力確保操做成功。網絡

Nebula-Jepsen 的測試程序目前分爲三種常見的測試模型和三種常見的錯誤注入。

Jepsen 測試模型

single-register

模擬一個寄存器,程序併發地對數據庫進行讀寫操做,每次成功的寫入操做都會使寄存器中存儲的值發生變化,而後經過對比每次從數據庫讀出的值是否和寄存器中記錄的值一致,來驗證結果是否知足線性要求。因爲寄存器是單一的,因此在此處咱們生成惟一的 key,隨機的 value 進行操做。

multi-register

一個能夠存不一樣鍵的寄存器。和單一寄存器的效果同樣,但此處咱們可使 key 也隨機生成了。

4       :invoke :write  [[:w 9 1]]
4       :ok     :write  [[:w 9 1]]
3       :invoke :read   [[:r 5 nil]]
3       :ok     :read   [[:r 5 3]]
0       :invoke :read   [[:r 7 nil]]
0       :ok     :read   [[:r 7 2]]
0       :invoke :write  [[:w 7 1]]
0       :ok     :write  [[:w 7 1]]
1       :invoke :read   [[:r 1 nil]]
1       :ok     :read   [[:r 1 4]]
0       :invoke :read   [[:r 8 nil]]
0       :ok     :read   [[:r 8 3]]
:nemesis        :info   :start  nil
:nemesis        :info   :start  [:isolated {"n5" #{"n2" "n1" "n4" "n3"}, "n2" #{"n5"}, "n1" #{"n5"}, "n4" #{"n5"}, "n3" #{"n5"}}]
1       :invoke :write  [[:w 4 2]]
1       :ok     :write  [[:w 4 2]]
2       :invoke :read   [[:r 5 nil]]
3       :invoke :write  [[:w 1 2]]
2       :ok     :read   [[:r 5 3]]
3       :ok     :write  [[:w 1 2]]
0       :invoke :read   [[:r 4 nil]]
0       :ok     :read   [[:r 4 2]]
1       :invoke :write  [[:w 6 4]]
1       :ok     :write  [[:w 6 4]]

以上片斷是截取的測試中一小部分不一樣的讀寫操做示例,

format

其中最左邊的數字是執行此次操做的 worker,也就是進程號。每發起一次操做,標誌都是 invoke,接下來一列會指出是 write 仍是 read操做,而以後一列的中括號內,則顯示了具體的操做,好比

  • :invoke :read   [[:r 1 nil]]就是讀取 key 爲 1 的值,由於是 invoke,操做剛剛開始,還不知道值是什麼,因此後面是 nil。
  • :ok     :read   [[:r 1 4]] 中的 ok 則表示操做成功,能夠看到讀取到鍵 1 對應的值是 4。

在這個片斷中,還能夠看到一次 nemesis 被注入的時刻。

  • :nemesis   :info   :start  nil 標誌着 nemesis 的開始,後面的的內容 (:isolated ...) 表示了節點 n5 從整個集羣中被隔離,沒法與其餘 DB 節點進行網絡通訊。

cas-register

這是一個驗證 CAS 操做的寄存器。除了讀寫操做外,此次咱們還加入了隨機生成的 CAS 操做,cas-register 將會對結果進行線性分析。

0		:invoke	:read		nil
0		:ok			:read		0
1		:invoke	:cas		[0 2]
1		:ok			:cas		[0 2]
4		:invoke	:read		nil
4		:ok			:read		2
0		:invoke	:read		nil
0		:ok			:read		2
2		:invoke	:write	0
2		:ok			:write	0
3		:invoke	:cas		[2 2]
:nemesis		:info		:start	nil
0		:invoke	:read		nil
0		:ok			:read		0
1		:invoke	:cas		[1 3]
:nemesis		:info		:start	{"n1" ""}
3		:fail		:cas		[2 2]
1		:fail		:cas		[1 3]
4		:invoke	:read		nil
4		:ok			:read		0

一樣的,在此次測試中,咱們採用惟一的鍵值,好比全部寫入和讀取操做都是對鍵 "f" 執行,在顯示上省略了中括號中的鍵,只顯示是什麼值。

  • :invoke :read  nil 表示開始一次讀取 「f」 的值的操做,由於剛開始操做,因此結果是 nil(空)。
  • :ok   :read  0 表示成功讀取到了鍵 「f」 的值爲 0。
  • :invoke :cas  [1 2] 意思是進行 CAS 操做,當讀到的值爲 1 時,將值改成 2。

在第二行能夠看到,當保存的 value 是 0 時,在第 4 行 cas[0 2] 會將 value 變爲 2。在第 14 行當值爲 0時,17 行的 cas[2 2] 就失敗了。

第 16 行顯示了 n1 節點被殺掉的操做,第 1七、18 行會有兩個 cas 失敗(fail)

Jepsen 錯誤注入

kill-node

Jepsen 的控制節點會在整個測試過程當中,屢次隨機 kill 某一節點中的數據庫服務而使服務中止。此時集羣中就少了一個節點。而後在必定時間後再將該節點的數據庫服務啓動,使之從新加入集羣。

partition-random-node

Jepsen 會在測試過程當中,屢次隨機將某一節點與其餘節點網絡隔離,使該節點沒法與其餘節點通訊,其餘節點也沒法和它通訊。而後在必定時間後再恢復這一網絡隔離,使集羣恢復原狀。

partition-random-halves

在這種常見的網絡分區情景下,Jepsen 控制節點會將 5 個 DB 節點隨機分紅兩部分,一部分爲兩個節點,另外一部分爲三個。必定時間後恢復通訊。以下圖所示。

partition

測試結束後

Jepsen 會根據需求對測試結果進行分析,並得出本次測試的結果,能夠看到控制檯的輸出,本次測試是經過的。

2020-01-08 03:24:51,742{GMT}	INFO	[jepsen test runner] jepsen.core: {:timeline {:valid? true},
 :linear
 {:valid? true,
  :configs
  ({:model {:value 0},
    :last-op
    {:process 0,
     :type :ok,
     :f :write,
     :value 0,
     :index 597,
     :time 60143184600},
    :pending []}),
  :analyzer :linear,
  :final-paths ()},
 :valid? true}


Everything looks good! ヽ(‘ー`)ノ

自動生成的 timeline.html 文件

Jepsen 在測試執行過程當中會自動生成一個名爲 timeline.html 文件,如下爲本次實踐生成的 timeline.html 文件部分截圖

timeline

上面的圖片展現了測試中執行操做的時間軸片斷,每一個執行塊有對應的執行信息,Jepsen 會將整個時間軸生成一個 HTML 文件。

Jepsen 就是這樣按照順序的歷史操做記錄進行 Linearizability 一致性驗證,這也是 Jepsen 的核心。咱們也能夠經過這個 HTML 文件來幫助咱們溯源錯誤。

Jepsen 生成的性能分析圖

下面是一些 Jepsen 生成的性能分析圖表,本次實踐項目名爲「basic-test」各位讀者閱讀時請自行腦補爲你項目名。

latency

能夠看到,這一張圖表展現了 Nebula Graph 的讀寫操做延時。其中上方灰色的區域是錯誤注入的時段,在本次測試咱們注入了隨機 kill node。

rate

而在這一張圖展現了讀寫操做的成功率,咱們能夠看出,最下方紅色集中突出的地方爲出現失敗的地方,這是由於 control node 在殺死節點時終止了某個 partition 的 leader 中的 nebula 服務。集羣此時須要從新選舉,在選舉出新的 leader 以後,讀寫操做也恢復到正常了。

經過觀察測試程序運行結果和分析圖表,能夠看到 Nebula Graph 完成了本次在單寄存器模型中注入 kill-node 錯誤的測試,讀寫操做延時也均處於正常範圍。

小結

Jepsen 自己也存在一些不足,好比測試沒法長時間運行,由於大量數據在校驗階段會形成 Out of Memory。

但在實際場景中,許多 bug 須要長時間的壓力測試、故障模擬才能發現,同時系統的穩定性也須要長時間的運行才能被驗證。但與此同時,在使用 Jepsen 對 Nebula Graph 進行測試的過程當中,咱們也發現了一些以前沒有遇到過的 Bug,甚至其中一些在使用中可能永遠也不會出現。

目前,咱們已經在平常開發過程當中使用 Jepsen 對 Nebula Graph 進行測試。Nebula Graph 有代碼更新後,每晚都將編譯好的項目發佈在 Docker Hub 中,Nebula-Jepsen 將自動下拉最新的鏡像進行持續測試。

最後是 Nebula 的 GitHub 地址,歡迎你們試用,有什麼問題能夠向咱們提 issue。GitHub 地址:https://github.com/vesoft-inc/nebula, 加入 Nebula Graph 交流羣,請聯繫 Nebula Graph 官方小助手微信號:NebulaGraphbot

參考文獻

相關文章
相關標籤/搜索