Jepsen:分布式系统最早的混沌框架
來(lái)源 | 混沌工程實(shí)踐
作者 |?羅岡庭
頭圖 | 下載于ICphoto
Jepsen測(cè)試框架的工作模式和混沌工程的思想是一脈相承的。Jepsen測(cè)試框架可以在分布式系統(tǒng)上注入眾多混沌事件,例如引入網(wǎng)絡(luò)問題、殺死節(jié)點(diǎn)和生成隨機(jī)負(fù)載等等,然后通過執(zhí)行預(yù)先定義的測(cè)試操作,根據(jù)過程記錄和結(jié)果分析,發(fā)現(xiàn)分布式系統(tǒng)(主要是數(shù)據(jù)庫(kù)、協(xié)調(diào)服務(wù)和隊(duì)列)中潛在的一致性問題。
Jepsen 是什么
根據(jù)CAP定理,一致性、可用性和分區(qū)容錯(cuò)性,分布式系統(tǒng)只能實(shí)現(xiàn)這三個(gè)關(guān)鍵屬性中的兩個(gè)。大多數(shù)分布式系統(tǒng)不會(huì)放棄分區(qū)容錯(cuò)性,一致性和可用性的權(quán)衡,像數(shù)據(jù)存儲(chǔ)服務(wù)則偏向于一致性。
Jepsen致力于提高分布式數(shù)據(jù)庫(kù)、隊(duì)列和共識(shí)協(xié)調(diào)系統(tǒng)等的一致性。Jepsen是由凱爾·金斯伯里(Kyle Kingsbury)采用函數(shù)式編程語(yǔ)言Clojure編寫的測(cè)試框架,用來(lái)驗(yàn)證分布式系統(tǒng)的一致性。Jepsen于2015年開源。(https://github.com/jepsen-io/jepsen)
Kyle也是開源分布式監(jiān)控工具Riemann的創(chuàng)始人,可能大家沒有聽過Riemann。《監(jiān)控的藝術(shù)》(The Art of Monitoring),這本書投入蠻多篇幅來(lái)介紹這款低延遲的監(jiān)控系統(tǒng)。
Jepsen已被用來(lái)測(cè)試眾多分布式系統(tǒng),例如MongoDB、ElasticSearch和Zookeeper。
自2013年以來(lái),Jepsen已經(jīng)分析了二十多個(gè)業(yè)界知名的數(shù)據(jù)庫(kù)、協(xié)調(diào)服務(wù)和隊(duì)列,發(fā)現(xiàn)了副本分叉(replica divergence)、數(shù)據(jù)丟失(data loss)、過期讀取(stale reads)、讀偏序(read skew)、鎖沖突(lock conflicts)等等多類型的一致性問題。(https://jepsen.io/analyses)
Clojure 的難度
Jepsen本身基于Clojure開發(fā),想要深入了解該框架的內(nèi)部實(shí)現(xiàn)以及上述業(yè)界知名分布式系統(tǒng)的Jespen測(cè)試代碼,就需要學(xué)會(huì)Clojure。Clojure是一種基于JVM的函數(shù)式編程語(yǔ)言,跟Java可以進(jìn)行很好的交互。Jepsen的作者Kyle也寫過一篇關(guān)于Clojure入門相關(guān)的文章。(https://aphyr.com/posts/301-clojure-from-the-ground-up-welcome)
很多人聽到函數(shù)式編程,就開始瑟瑟發(fā)抖,其實(shí)大可不必,因?yàn)閷?duì)于Jepsen的使用者而言,看得懂,會(huì)修改現(xiàn)有的例子,往往都能滿足大多數(shù)的需求,不過這確實(shí)在一定程度上影響了Jepsen在這個(gè)領(lǐng)域的應(yīng)用范圍。
這里就不詳細(xì)介紹Clojure的安裝和配置了。
Jepsen 的工作模式
如下圖所示,Jepsen運(yùn)行在控制節(jié)點(diǎn)(Control Node)上,測(cè)試過程中會(huì)啟用生成器(Generator)進(jìn)程、SSH客戶端(Client)、克星(Nemesis)進(jìn)程和檢查器(Checker)進(jìn)程。
以分布式數(shù)據(jù)庫(kù)集群為例,Jepsen的工作大致包含以下幾步:
Jepsen在控制節(jié)點(diǎn)(Control Node)上作為Clojure程序運(yùn)行;
部署需要測(cè)試的分布式數(shù)據(jù)庫(kù)集群(Distributed System),確認(rèn)其工作正常;
控制節(jié)點(diǎn)啟動(dòng)進(jìn)程,作為分布式數(shù)據(jù)庫(kù)節(jié)點(diǎn)(DB Node)的SSH客戶端;
生成器(Generator)進(jìn)程為每個(gè)SSH客戶端生成具體要執(zhí)行的讀寫操作;
這些SSH客戶端將在分布式數(shù)據(jù)庫(kù)中執(zhí)行這些讀寫操作;
每個(gè)操作從啟動(dòng)到結(jié)束的過程都會(huì)記錄下來(lái);
當(dāng)讀寫操作進(jìn)行時(shí),克星進(jìn)程(Nemesis)對(duì)分布式數(shù)據(jù)庫(kù)進(jìn)行故障注入,同樣也是由生成器來(lái)調(diào)度和管理;
測(cè)試完成,分布式數(shù)據(jù)庫(kù)集群會(huì)被銷毀。Jepsen使用檢查器(Checker)進(jìn)程對(duì)測(cè)試過程的歷史記錄進(jìn)行分析,并生成最終的圖表和報(bào)告。
Jepsen 與混沌工程
采用基于經(jīng)驗(yàn)和系統(tǒng)的方法解決分布式系統(tǒng)在規(guī)模增長(zhǎng)時(shí)引發(fā)的問題, 并以此建立對(duì)系統(tǒng)抵御這些事件的能力和信心。在受控實(shí)驗(yàn)中觀察分布式系統(tǒng)的行為,借此了解系統(tǒng)特性,我們稱之為混沌工程。
混沌工程是在分布式系統(tǒng)上進(jìn)行實(shí)驗(yàn)的學(xué)科, 目的是建立對(duì)系統(tǒng)抵御生產(chǎn)環(huán)境中失控條件的能力以及信心。
前面我們了解了Jepsen的工作模式,這和混沌工程的思想是一脈相承的。
Jepsen可以在分布式系統(tǒng)上注入眾多混沌事件,例如引入網(wǎng)絡(luò)問題、殺死節(jié)點(diǎn)和生成隨機(jī)負(fù)載等等,然后通過執(zhí)行預(yù)先定義的測(cè)試操作,發(fā)現(xiàn)分布式系統(tǒng)(主要是數(shù)據(jù)庫(kù)、協(xié)調(diào)服務(wù)和隊(duì)列)中的一致性問題。
詳細(xì)的Jepsen介紹文檔可以參考這里:https://github.com/jepsen-io/jepsen/blob/main/doc/tutorial/index.md
Hello World 示例
今天,我們先以一個(gè)空白場(chǎng)景為例,感受下Jepsen的用法。
在容器中進(jìn)行Jepsen測(cè)試,需要多種類型的容器配合部署:
jepsen-control: 控制節(jié)點(diǎn),管理其他節(jié)點(diǎn),創(chuàng)建和銷毀,生成測(cè)試操作和故障注入場(chǎng)景
jepsen-nX: 目標(biāo)分布式數(shù)據(jù)庫(kù)集群(默認(rèn)是5個(gè)節(jié)點(diǎn))
jepsen-node: 克星節(jié)點(diǎn),負(fù)責(zé)故障注入
下面的鏈接中提供了docker-compose up的方式,快速部署個(gè)Jepsen測(cè)試環(huán)境:https://github.com/jepsen-io/jepsen/tree/main/docker
完成部署之后,要在容器中運(yùn)行 Jepsen 測(cè)試,則需要通過以下命令
$ docker exec-ti jepsen-control bash進(jìn)入jepsen-control節(jié)點(diǎn),該容器中已經(jīng)安裝了Java和Clojure運(yùn)行環(huán)境,以及l(fā)ein(Clojure集成開發(fā)工具),然后就創(chuàng)建一個(gè)etcd的示例測(cè)試項(xiàng)目:
$ lein new jepsen.etcdemoGenerating a project called jepsen.etcdemo based on the 'default'template.Thedefaulttemplateis intended for library projects, not applications.To see other templates (app, plugin, etc), try`lein help new`.$ cd jepsen.etcdemo$ lsCHANGELOG.md doc/ LICENSE project.clj README.md resources/ src/ test/像任何新的Clojure項(xiàng)目一樣,項(xiàng)目文件夾下包含:
一個(gè)空白的變更日志
一個(gè)文檔目錄
一份 Eclipse 公共許可證的副本
一個(gè)?project.clj,說(shuō)明了lein如何構(gòu)建和運(yùn)行代碼
一個(gè)描述文件
一個(gè)resources目錄,存放數(shù)據(jù)文件的地方,例如:要測(cè)試的數(shù)據(jù)庫(kù)的配置文件。
一個(gè)src目錄,存有源代碼
一個(gè)test目錄,里面是大多數(shù) Clojure 庫(kù)的測(cè)試約定,我們不會(huì)在這里使用它。
先對(duì)project.clj進(jìn)行修改,指定項(xiàng)目的依賴項(xiàng)和其他元數(shù)據(jù)。添加一個(gè):main的命名空間jepsen.etcdemo,這就是我們從命令行運(yùn)行測(cè)試的入口。
除了依賴Clojure語(yǔ)言本身,我們還將引入2個(gè)依賴庫(kù):Jepsen和Verschlimmbesserung,后者用于與etcd的交互。
(defproject jepsen.etcdemo "0.1.0-SNAPSHOT":description "A Jepsen test for etcd":license {:name "Eclipse Public License":url "http://www.eclipse.org/legal/epl-v10.html"}:main jepsen.etcdemo:dependencies [[org.clojure/clojure "1.10.0"][jepsen "0.2.1-SNAPSHOT"][verschlimmbesserung "0.1.3"]])我們來(lái)執(zhí)行下lein run
$ lein runExceptionin thread "main" java.lang.Exception: Cannot find anything to run for: jepsen.etcdemo, compiling:(/tmp/form-init6673004597601163646.clj:1:73)...回想下,剛添加一個(gè):main的命名空間jepsen.etcdemo,里面現(xiàn)在還沒有東西,我們需要在src/jepsen/etcdemo.clj中添加具體的測(cè)試操作。
(ns jepsen.etcdemo)(defn -main"Handles command line arguments. Can either run a test, or a web server for browsing results."[& args](prn "Hello, world!" args))我們?cè)賮?lái)執(zhí)行下lein run
$ lein run hi there"Hello, world!"("hi""there")可以跑起來(lái)了!
讓我們使用jepsen.cli命名空間,簡(jiǎn)稱cli,并將我們的main函數(shù)轉(zhuǎn)換為Jepsen測(cè)試運(yùn)行程序:
(ns jepsen.etcdemo(:require[jepsen.cli :as cli][jepsen.tests :as tests]))(defn etcd-test"Given an options map from the command line runner (e.g. :nodes, :ssh,:concurrency, ...), constructs a test map."[opts](merge tests/noop-test{:pure-generators true} opts))(defn -main"Handles command line arguments. Can either run a test, or a web server for browsing results."[& args](cli/run! (cli/single-test-cmd {:test-fn etcd-test}) args))cli/single-test-cmd由jepsen.cli提供,將會(huì)解析命令行參數(shù),并調(diào)用:test-fn,該函數(shù)會(huì)返回一個(gè)包含Jepsen測(cè)試所需的所有信息映射。在這種情況下,測(cè)試函數(shù)etcd-test接受命令行參數(shù),這里選擇的是空測(cè)試,即noop-test。
是時(shí)候跑一下測(cè)試看看了!
$ lein run test13:04:30.927[main] INFO jepsen.cli - Test options:{:concurrency 5,:test-count 1,:time-limit 60,:nodes ["n1""n2""n3""n4""n5"],:ssh{:username "root",:password "root",:strict-host-key-checking false,:private-key-path nil}}INFO [2018-02-0213:04:30,994] jepsen test runner - jepsen.core Running test:...INFO [2018-02-0213:04:35,389] jepsen nemesis - jepsen.core Starting nemesisINFO [2018-02-0213:04:35,389] jepsen worker 1- jepsen.core Starting worker 1INFO [2018-02-0213:04:35,389] jepsen worker 2- jepsen.core Starting worker 2INFO [2018-02-0213:04:35,389] jepsen worker 0- jepsen.core Starting worker 0INFO [2018-02-0213:04:35,390] jepsen worker 3- jepsen.core Starting worker 3INFO [2018-02-0213:04:35,390] jepsen worker 4- jepsen.core Starting worker 4INFO [2018-02-0213:04:35,391] jepsen nemesis - jepsen.core Running nemesisINFO [2018-02-0213:04:35,391] jepsen worker 1- jepsen.core Running worker 1INFO [2018-02-0213:04:35,391] jepsen worker 2- jepsen.core Running worker 2INFO [2018-02-0213:04:35,391] jepsen worker 0- jepsen.core Running worker 0INFO [2018-02-0213:04:35,391] jepsen worker 3- jepsen.core Running worker 3INFO [2018-02-0213:04:35,391] jepsen worker 4- jepsen.core Running worker 4INFO [2018-02-0213:04:35,391] jepsen nemesis - jepsen.core Stopping nemesisINFO [2018-02-0213:04:35,391] jepsen worker 1- jepsen.core Stopping worker 1INFO [2018-02-0213:04:35,391] jepsen worker 2- jepsen.core Stopping worker 2INFO [2018-02-0213:04:35,391] jepsen worker 0- jepsen.core Stopping worker 0INFO [2018-02-0213:04:35,391] jepsen worker 3- jepsen.core Stopping worker 3INFO [2018-02-0213:04:35,391] jepsen worker 4- jepsen.core Stopping worker 4INFO [2018-02-0213:04:35,397] jepsen test runner - jepsen.core Run complete, writingINFO [2018-02-0213:04:35,434] jepsen test runner - jepsen.core AnalyzingINFO [2018-02-0213:04:35,435] jepsen test runner - jepsen.core Analysis completeINFO [2018-02-0213:04:35,438] jepsen results - jepsen.store Wrote/home/aphyr/jepsen/jepsen.etcdemo/store/noop/20180202T130430.000-0600/results.ednINFO [2018-02-0213:04:35,440] main - jepsen.core {:valid? true}Everything looks good! ヽ(‘ー`)ノ我們可以看到,Jepsen啟動(dòng)了一系列SSH客戶端進(jìn)程,負(fù)責(zé)對(duì)數(shù)據(jù)庫(kù)執(zhí)行測(cè)試操作,還有就是啟用了故障注入的克星進(jìn)程。這里我們并沒有執(zhí)行任何操作,所以立即停止了。此后,Jepsen會(huì)將這個(gè)測(cè)試結(jié)果寫到store目錄中,并打印出一個(gè)簡(jiǎn)短的分析。
$ ls store/latest/history.txt jepsen.log results.edn test.fressianhistory.txt顯示測(cè)試執(zhí)行的操作,這里應(yīng)該是空的,因?yàn)閚oop-test不執(zhí)行任何操作;jepsen.log存有該測(cè)試的控制臺(tái)日志;results.edn包含了對(duì)測(cè)試的分析;最后,test.fressian助力事后分析的測(cè)試原始數(shù)據(jù),包括完整的機(jī)器可讀的歷史記錄和分析結(jié)果。
若此時(shí)遇到SSH錯(cuò)誤,應(yīng)檢查SSH客戶端是否正常運(yùn)行,并且是否已加載所有節(jié)點(diǎn)的密鑰。
$ lein run test --help#object[jepsen.cli$test_usage 0x7ddd84b5 jepsen.cli$test_usage@7ddd84b5]-h, --help Printoutthis message andexit-n, --node HOSTNAME ["n1""n2""n3""n4""n5"] Node(s) to run test on--nodes-file FILENAME File containing node hostnames, one per line.--username USER root Usernamefor logins--password PASS root Passwordfor sudo access--strict-host-key-checking Whether to check host keys--ssh-private-key FILE Path to an SSH identity file--concurrency NUMBER 1nHow many workers should we run? Must be an integer, optionally followed by n (e.g. 3n) to multiply by the number of nodes.--test-count NUMBER 1How many times should we repeat a test?--time-limit SECONDS 60Excluding setup and teardown, how long should a test run for, in seconds?最后,我們就可以銷毀分布式系統(tǒng)集群的所有節(jié)點(diǎn)了。
結(jié)束語(yǔ)
本文我們介紹Jepsen測(cè)試框架的來(lái)歷、基本的工作模式、與混沌工程的關(guān)系,也體會(huì)了一下Jepsen的Hello World示例。
雖然我們還沒有真正選擇一個(gè)分布式系統(tǒng),進(jìn)行實(shí)際的測(cè)試操作,也沒有同時(shí)進(jìn)行故障注入,但至少我們體驗(yàn)了一把Jepsen測(cè)試所需要的函數(shù)式編程語(yǔ)法。
后面我們會(huì)以一個(gè)具體的例子來(lái)詳實(shí)地分享,如何使用Jepsen檢驗(yàn)分布式系統(tǒng)的一致性問題。
? ?更多精彩推薦??? 云上數(shù)據(jù)保護(hù),你以為擋住黑客就夠了?在這次采訪中,Mendix 披露了低代碼方法論DeVOpS 實(shí)戰(zhàn):Kubernetes 微服務(wù)監(jiān)控體系還在擔(dān)心無(wú)代碼是否威脅程序員飯碗?總結(jié)
以上是生活随笔為你收集整理的Jepsen:分布式系统最早的混沌框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 局部性原理
- 下一篇: 工作中使用了一些触发器