您好,登錄后才能下訂單哦!
Jepsen 測試框架在圖數據庫 Nebula Graph中的實踐分析,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
Jepsen 是一款用于系統測試的開源軟件庫,致力于提高分布式數據庫、隊列、共識系統等的安全性。作者 Kyle Kingsbury 使用函數式編程語言 Clojure 編寫了這款測試框架,并對多個著名的分布式系統和數據庫進行了一致性測試。目前 Jepsen 仍在 GitHub 保持活躍,能否通過 Jepsen 的測試已經成為各個分布式數據庫對自身檢驗的一個標桿。
Jepsen 測試推薦使用 Docker 搭建集群。默認情況下由 6 個 container 組成,其中一個是控制節點(control node),另外 5 個是數據庫的節點(默認為 n1-n5)。控制節點在測試程序開始后會啟用多個 worker 進程,并發地通過 SSH 登入數據庫節點進行讀寫操作。
測試開始后,控制節點會創建一組進程,進程包含了待測試分布式系統的客戶端。另一個 Generator 進程產生每個客戶端執行的操作,并將操作應用于待測試的分布式系統。每個操作的開始和結束以及操作結果記錄在歷史記錄中。同時,一個特殊進程 Nemesis 將故障引入系統。
測試結束后,Checker 分析歷史記錄是否正確,是否符合一致性。用戶可以使用 Jepsen 的 knossos 中提供的驗證模型,也可以自己定義符合需求的模型對測試結果進行驗證。同時,還可以在測試中注入錯誤對集群進行干擾測試。
最后根據本次測試所規定的驗證模型對結果進行分析。
使用 Jepsen 過程中可能會遇到一些問題,可以參考一下使用 Tips:
在 Jepsen 框架中,用戶需要在 DB 接口中對自己的數據庫定義下載,安裝,啟動與終止操作。在終止后,可以將 log 文件清除,同時也可以指定 log 的存儲位置,Jepsen 會將其拷貝至 Jepsen 的 log 文件夾中,以便連同 Jepsen 自身的 log 進行分析。
用戶還需要提供訪問自己數據庫的客戶端,這個客戶端可以是你用 Clojure 實現的,比如 etcd 的 verschlimmbesserung,也可以是 JDBC,等等。然后需要定義 Client 接口,告訴 Jepsen 如何對你的數據庫進行操作。
在 Checker 中,你可以選擇需要的測試模型,比如,性能測試(checker/perf)將會生成 latency 和整個測試過程的圖表,時間軸(timeline/html)會生成一個記錄著所有操作時間軸的 html 頁面。
另外一個不可或缺的組件就是在 nemesis 中注入想要測試的錯誤了。網絡分區(nemesis/partition-random-halves)和殺掉數據節點(kill-node)是比較常見的注入錯誤。
在 Generator 中,用戶可以告知 worker 進程需要生成哪些操作,每一次操作的時間間隔,每一次錯誤注入的時間間隔等等。
分布式圖數據庫 Nebula Graph 主要由 3 部分組成,分別是 meta 層,graph 層和 storage 層。
我們在使用 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 的測試程序目前分為三種常見的測試模型和三種常見的錯誤注入。
模擬一個寄存器,程序并發地對數據庫進行讀寫操作,每次成功的寫入操作都會使寄存器中存儲的值發生變化,然后通過對比每次從數據庫讀出的值是否和寄存器中記錄的值一致,來驗證結果是否滿足線性要求。由于寄存器是單一的,所以在此處我們生成唯一的 key,隨機的 value 進行操作。
一個可以存不同鍵的寄存器。和單一寄存器的效果一樣,但此處我們可以使 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]]
以上片段是截取的測試中一小部分不同的讀寫操作示例,
其中最左邊的數字是執行這次操作的 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 操作的寄存器。除了讀寫操作外,這次我們還加入了隨機生成的 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 節點被殺掉的操作,第 17、18 行會有兩個 cas 失敗(fail)
Jepsen 的控制節點會在整個測試過程中,多次隨機 kill 某一節點中的數據庫服務而使服務停止。此時集群中就少了一個節點。然后在一定時間后再將該節點的數據庫服務啟動,使之重新加入集群。
Jepsen 會在測試過程中,多次隨機將某一節點與其他節點網絡隔離,使該節點無法與其他節點通信,其他節點也無法和它通信。然后在一定時間后再恢復這一網絡隔離,使集群恢復原狀。
在這種常見的網絡分區情景下,Jepsen 控制節點會將 5 個 DB 節點隨機分成兩部分,一部分為兩個節點,另一部分為三個。一定時間后恢復通信。如下圖所示。
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! ヽ(‘ー`)ノ
Jepsen 在測試執行過程中會自動生成一個名為 timeline.html 文件,以下為本次實踐生成的 timeline.html 文件部分截圖
上面的圖片展示了測試中執行操作的時間軸片段,每個執行塊有對應的執行信息,Jepsen 會將整個時間軸生成一個 HTML 文件。
Jepsen 就是這樣按照順序的歷史操作記錄進行 Linearizability 一致性驗證,這也是 Jepsen 的核心。我們也可以通過這個 HTML 文件來幫助我們溯源錯誤。
下面是一些 Jepsen 生成的性能分析圖表,本次實踐項目名為「basic-test」各位讀者閱讀時請自行腦補為你項目名。
可以看到,這一張圖表展示了 Nebula Graph 的讀寫操作延時。其中上方灰色的區域是錯誤注入的時段,在本次測試我們注入了隨機 kill node。
而在這一張圖展示了讀寫操作的成功率,我們可以看出,最下方紅色集中突出的地方為出現失敗的地方,這是因為 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 將自動下拉最新的鏡像進行持續測試。
關于Jepsen 測試框架在圖數據庫 Nebula Graph中的實踐分析問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。