溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Jepsen 測(cè)試框架在圖數(shù)據(jù)庫(kù) Nebula Graph中的實(shí)踐分析

發(fā)布時(shí)間:2021-11-30 17:00:54 來(lái)源:億速云 閱讀:139 作者:柒染 欄目:數(shù)據(jù)庫(kù)

Jepsen 測(cè)試框架在圖數(shù)據(jù)庫(kù) Nebula Graph中的實(shí)踐分析,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

Jepsen 簡(jiǎn)介

Jepsen 是一款用于系統(tǒng)測(cè)試的開(kāi)源軟件庫(kù),致力于提高分布式數(shù)據(jù)庫(kù)、隊(duì)列、共識(shí)系統(tǒng)等的安全性。作者 Kyle Kingsbury 使用函數(shù)式編程語(yǔ)言 Clojure 編寫(xiě)了這款測(cè)試框架,并對(duì)多個(gè)著名的分布式系統(tǒng)和數(shù)據(jù)庫(kù)進(jìn)行了一致性測(cè)試。目前 Jepsen 仍在 GitHub 保持活躍,能否通過(guò) Jepsen 的測(cè)試已經(jīng)成為各個(gè)分布式數(shù)據(jù)庫(kù)對(duì)自身檢驗(yàn)的一個(gè)標(biāo)桿。

Jepsen 的測(cè)試流程

Jepsen 測(cè)試推薦使用 Docker 搭建集群。默認(rèn)情況下由 6 個(gè) container 組成,其中一個(gè)是控制節(jié)點(diǎn)(control node),另外 5 個(gè)是數(shù)據(jù)庫(kù)的節(jié)點(diǎn)(默認(rèn)為 n1-n5)??刂乒?jié)點(diǎn)在測(cè)試程序開(kāi)始后會(huì)啟用多個(gè) worker 進(jìn)程,并發(fā)地通過(guò) SSH 登入數(shù)據(jù)庫(kù)節(jié)點(diǎn)進(jìn)行讀寫(xiě)操作。

測(cè)試開(kāi)始后,控制節(jié)點(diǎn)會(huì)創(chuàng)建一組進(jìn)程,進(jìn)程包含了待測(cè)試分布式系統(tǒng)的客戶(hù)端。另一個(gè) Generator 進(jìn)程產(chǎn)生每個(gè)客戶(hù)端執(zhí)行的操作,并將操作應(yīng)用于待測(cè)試的分布式系統(tǒng)。每個(gè)操作的開(kāi)始和結(jié)束以及操作結(jié)果記錄在歷史記錄中。同時(shí),一個(gè)特殊進(jìn)程 Nemesis 將故障引入系統(tǒng)。

測(cè)試結(jié)束后,Checker 分析歷史記錄是否正確,是否符合一致性。用戶(hù)可以使用 Jepsen 的 knossos 中提供的驗(yàn)證模型,也可以自己定義符合需求的模型對(duì)測(cè)試結(jié)果進(jìn)行驗(yàn)證。同時(shí),還可以在測(cè)試中注入錯(cuò)誤對(duì)集群進(jìn)行干擾測(cè)試。

最后根據(jù)本次測(cè)試所規(guī)定的驗(yàn)證模型對(duì)結(jié)果進(jìn)行分析。

如何使用 Jepsen

使用 Jepsen 過(guò)程中可能會(huì)遇到一些問(wèn)題,可以參考一下使用 Tips:

  1. 在 Jepsen 框架中,用戶(hù)需要在 DB 接口中對(duì)自己的數(shù)據(jù)庫(kù)定義下載,安裝,啟動(dòng)與終止操作。在終止后,可以將 log 文件清除,同時(shí)也可以指定 log 的存儲(chǔ)位置,Jepsen 會(huì)將其拷貝至 Jepsen 的 log 文件夾中,以便連同 Jepsen 自身的 log 進(jìn)行分析。

  2. 用戶(hù)還需要提供訪(fǎng)問(wèn)自己數(shù)據(jù)庫(kù)的客戶(hù)端,這個(gè)客戶(hù)端可以是你用 Clojure 實(shí)現(xiàn)的,比如 etcd 的 verschlimmbesserung,也可以是 JDBC,等等。然后需要定義 Client 接口,告訴 Jepsen 如何對(duì)你的數(shù)據(jù)庫(kù)進(jìn)行操作。

  3. 在 Checker 中,你可以選擇需要的測(cè)試模型,比如,性能測(cè)試(checker/perf)將會(huì)生成 latency 和整個(gè)測(cè)試過(guò)程的圖表,時(shí)間軸(timeline/html)會(huì)生成一個(gè)記錄著所有操作時(shí)間軸的 html 頁(yè)面。

  4. 另外一個(gè)不可或缺的組件就是在 nemesis 中注入想要測(cè)試的錯(cuò)誤了。網(wǎng)絡(luò)分區(qū)(nemesis/partition-random-halves)和殺掉數(shù)據(jù)節(jié)點(diǎn)(kill-node)是比較常見(jiàn)的注入錯(cuò)誤。

  5. 在 Generator 中,用戶(hù)可以告知 worker 進(jìn)程需要生成哪些操作,每一次操作的時(shí)間間隔,每一次錯(cuò)誤注入的時(shí)間間隔等等。

用 Jepsen 測(cè)試圖數(shù)據(jù)庫(kù) Nebula Graph

分布式圖數(shù)據(jù)庫(kù) Nebula Graph 主要由 3 部分組成,分別是 meta 層,graph 層和 storage 層。

我們?cè)谑褂?Jepsen 對(duì) kv 存儲(chǔ)接口進(jìn)行的測(cè)試中,搭建了一個(gè)由 8 個(gè) container 組成的集群:一個(gè) Jepsen 的控制節(jié)點(diǎn),一個(gè) meta 節(jié)點(diǎn),一個(gè) graph 節(jié)點(diǎn),和 5 個(gè) storage 節(jié)點(diǎn),集群由 Docker-compose 啟動(dòng)。需要注意的是,要建立一個(gè)集群的 subnet 網(wǎng)絡(luò),使集群可以連通,另外要安裝 ssh 服務(wù),并為 control node 與 5 個(gè) storage 節(jié)點(diǎn)配置免密登入。

測(cè)試中使用了 Java 編寫(xiě)的客戶(hù)端程序,生成 jar 包并加入到 Clojure 程序依賴(lài),來(lái)對(duì) DB 進(jìn)行 put,get 和 cas (compare-and-set) 操作。另外 Nebula Graph 的客戶(hù)端有自動(dòng)重試邏輯,當(dāng)遇到錯(cuò)誤導(dǎo)致操作失敗時(shí),客戶(hù)端會(huì)啟用適當(dāng)?shù)闹卦嚈C(jī)制以盡力確保操作成功。

Nebula-Jepsen 的測(cè)試程序目前分為三種常見(jiàn)的測(cè)試模型和三種常見(jiàn)的錯(cuò)誤注入。

Jepsen 測(cè)試模型

single-register

模擬一個(gè)寄存器,程序并發(fā)地對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀寫(xiě)操作,每次成功的寫(xiě)入操作都會(huì)使寄存器中存儲(chǔ)的值發(fā)生變化,然后通過(guò)對(duì)比每次從數(shù)據(jù)庫(kù)讀出的值是否和寄存器中記錄的值一致,來(lái)驗(yàn)證結(jié)果是否滿(mǎn)足線(xiàn)性要求。由于寄存器是單一的,所以在此處我們生成唯一的 key,隨機(jī)的 value 進(jìn)行操作。

multi-register

一個(gè)可以存不同鍵的寄存器。和單一寄存器的效果一樣,但此處我們可以使 key 也隨機(jī)生成了。

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]]

以上片段是截取的測(cè)試中一小部分不同的讀寫(xiě)操作示例,

其中最左邊的數(shù)字是執(zhí)行這次操作的 worker,也就是進(jìn)程號(hào)。每發(fā)起一次操作,標(biāo)志都是 invoke,接下來(lái)一列會(huì)指出是 write 還是 read操作,而之后一列的中括號(hào)內(nèi),則顯示了具體的操作,比如

  • :invoke :read   [[:r 1 nil]]就是讀取 key 為 1 的值,因?yàn)槭?invoke,操作剛剛開(kāi)始,還不知道值是什么,所以后面是 nil。

  • :ok     :read   [[:r 1 4]] 中的 ok 則表示操作成功,可以看到讀取到鍵 1 對(duì)應(yīng)的值是 4。

在這個(gè)片段中,還可以看到一次 nemesis 被注入的時(shí)刻。

  • :nemesis   :info   :start  nil 標(biāo)志著 nemesis 的開(kāi)始,后面的的內(nèi)容 (:isolated ...) 表示了節(jié)點(diǎn) n5 從整個(gè)集群中被隔離,無(wú)法與其他 DB 節(jié)點(diǎn)進(jìn)行網(wǎng)絡(luò)通信。

cas-register

這是一個(gè)驗(yàn)證 CAS 操作的寄存器。除了讀寫(xiě)操作外,這次我們還加入了隨機(jī)生成的 CAS 操作,cas-register 將會(huì)對(duì)結(jié)果進(jìn)行線(xiàn)性分析。

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

同樣的,在這次測(cè)試中,我們采用唯一的鍵值,比如所有寫(xiě)入和讀取操作都是對(duì)鍵 “f” 執(zhí)行,在顯示上省略了中括號(hào)中的鍵,只顯示是什么值。

  • :invoke    :read  nil 表示開(kāi)始一次讀取 “f” 的值的操作,因?yàn)閯傞_(kāi)始操作,所以結(jié)果是 nil(空)。

  • :ok      :read  0 表示成功讀取到了鍵 “f” 的值為 0。

  • :invoke    :cas  [1 2] 意思是進(jìn)行 CAS 操作,當(dāng)讀到的值為 1 時(shí),將值改為 2。

在第二行可以看到,當(dāng)保存的 value 是 0 時(shí),在第 4 行 cas[0 2] 會(huì)將 value 變?yōu)?2。在第 14 行當(dāng)值為 0時(shí),17 行的 cas[2 2] 就失敗了。

第 16 行顯示了 n1 節(jié)點(diǎn)被殺掉的操作,第 17、18 行會(huì)有兩個(gè) cas 失敗(fail)

Jepsen 錯(cuò)誤注入

kill-node

Jepsen 的控制節(jié)點(diǎn)會(huì)在整個(gè)測(cè)試過(guò)程中,多次隨機(jī) kill 某一節(jié)點(diǎn)中的數(shù)據(jù)庫(kù)服務(wù)而使服務(wù)停止。此時(shí)集群中就少了一個(gè)節(jié)點(diǎn)。然后在一定時(shí)間后再將該節(jié)點(diǎn)的數(shù)據(jù)庫(kù)服務(wù)啟動(dòng),使之重新加入集群。

partition-random-node

Jepsen 會(huì)在測(cè)試過(guò)程中,多次隨機(jī)將某一節(jié)點(diǎn)與其他節(jié)點(diǎn)網(wǎng)絡(luò)隔離,使該節(jié)點(diǎn)無(wú)法與其他節(jié)點(diǎn)通信,其他節(jié)點(diǎn)也無(wú)法和它通信。然后在一定時(shí)間后再恢復(fù)這一網(wǎng)絡(luò)隔離,使集群恢復(fù)原狀。

partition-random-halves

在這種常見(jiàn)的網(wǎng)絡(luò)分區(qū)情景下,Jepsen 控制節(jié)點(diǎn)會(huì)將 5 個(gè) DB 節(jié)點(diǎn)隨機(jī)分成兩部分,一部分為兩個(gè)節(jié)點(diǎn),另一部分為三個(gè)。一定時(shí)間后恢復(fù)通信。如下圖所示。

測(cè)試結(jié)束后

Jepsen 會(huì)根據(jù)需求對(duì)測(cè)試結(jié)果進(jìn)行分析,并得出本次測(cè)試的結(jié)果,可以看到控制臺(tái)的輸出,本次測(cè)試是通過(guò)的。

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! ヽ(‘ー`)ノ

自動(dòng)生成的 timeline.html 文件

Jepsen 在測(cè)試執(zhí)行過(guò)程中會(huì)自動(dòng)生成一個(gè)名為 timeline.html 文件,以下為本次實(shí)踐生成的 timeline.html 文件部分截圖

上面的圖片展示了測(cè)試中執(zhí)行操作的時(shí)間軸片段,每個(gè)執(zhí)行塊有對(duì)應(yīng)的執(zhí)行信息,Jepsen 會(huì)將整個(gè)時(shí)間軸生成一個(gè) HTML 文件。

Jepsen 就是這樣按照順序的歷史操作記錄進(jìn)行 Linearizability 一致性驗(yàn)證,這也是 Jepsen 的核心。我們也可以通過(guò)這個(gè) HTML 文件來(lái)幫助我們溯源錯(cuò)誤。

Jepsen 生成的性能分析圖

下面是一些 Jepsen 生成的性能分析圖表,本次實(shí)踐項(xiàng)目名為「basic-test」各位讀者閱讀時(shí)請(qǐng)自行腦補(bǔ)為你項(xiàng)目名。

可以看到,這一張圖表展示了 Nebula Graph 的讀寫(xiě)操作延時(shí)。其中上方灰色的區(qū)域是錯(cuò)誤注入的時(shí)段,在本次測(cè)試我們注入了隨機(jī) kill node。

而在這一張圖展示了讀寫(xiě)操作的成功率,我們可以看出,最下方紅色集中突出的地方為出現(xiàn)失敗的地方,這是因?yàn)?control node 在殺死節(jié)點(diǎn)時(shí)終止了某個(gè) partition 的 leader 中的 nebula 服務(wù)。集群此時(shí)需要重新選舉,在選舉出新的 leader 之后,讀寫(xiě)操作也恢復(fù)到正常了。

通過(guò)觀察測(cè)試程序運(yùn)行結(jié)果和分析圖表,可以看到 Nebula Graph 完成了本次在單寄存器模型中注入 kill-node 錯(cuò)誤的測(cè)試,讀寫(xiě)操作延時(shí)也均處于正常范圍。

Jepsen 本身也存在一些不足,比如測(cè)試無(wú)法長(zhǎng)時(shí)間運(yùn)行,因?yàn)榇罅繑?shù)據(jù)在校驗(yàn)階段會(huì)造成 Out of Memory。

但在實(shí)際場(chǎng)景中,許多 bug 需要長(zhǎng)時(shí)間的壓力測(cè)試、故障模擬才能發(fā)現(xiàn),同時(shí)系統(tǒng)的穩(wěn)定性也需要長(zhǎng)時(shí)間的運(yùn)行才能被驗(yàn)證。但與此同時(shí),在使用 Jepsen 對(duì) Nebula Graph 進(jìn)行測(cè)試的過(guò)程中,我們也發(fā)現(xiàn)了一些之前沒(méi)有遇到過(guò)的 Bug,甚至其中一些在使用中可能永遠(yuǎn)也不會(huì)出現(xiàn)。

目前,我們已經(jīng)在日常開(kāi)發(fā)過(guò)程中使用 Jepsen 對(duì) Nebula Graph 進(jìn)行測(cè)試。Nebula Graph 有代碼更新后,每晚都將編譯好的項(xiàng)目發(fā)布在 Docker Hub 中,Nebula-Jepsen 將自動(dòng)下拉最新的鏡像進(jìn)行持續(xù)測(cè)試。

關(guān)于Jepsen 測(cè)試框架在圖數(shù)據(jù)庫(kù) Nebula Graph中的實(shí)踐分析問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI