4.检查器

正确性校验

通过生成器和客户端执行一些操作,我们获取到了用于分析正确性的历史记录。Jepsen使用model代表系统的抽象行为,checker来验证历史记录是否符合该模型。我们需要knossos.modeljepsen.checker
1
(ns jepsen.etcdemo
2
(:require [clojure.tools.logging :refer :all]
3
[clojure.string :as str]
4
[jepsen [checker :as checker]
5
[cli :as cli]
6
[client :as client]
7
[control :as c]
8
[db :as db]
9
[generator :as gen]
10
[tests :as tests]]
11
[jepsen.control.util :as cu]
12
[jepsen.os.debian :as debian]
13
[knossos.model :as model]
14
[slingshot.slingshot :refer [try+]]
15
[verschlimmbesserung.core :as v]))
Copied!
还记得我们如何构建读、写和cas操作吗?
1
(defn r [_ _] {:type :invoke, :f :read, :value nil})
2
(defn w [_ _] {:type :invoke, :f :write, :value (rand-int 5)})
3
(defn cas [_ _] {:type :invoke, :f :cas, :value [(rand-int 5) (rand-int 5)]})
Copied!
Jepsen并不知道:f :read:f :cas的含义,就其而言,他们可以是任意值。然而,当它基于(case (:f op) :read ...)进行控制流转时,我们的client知道如何解释这些操作。现在,我们需要一个能够理解这些相同操作的系统模型。Knossos已经为我们定义好了模型数据类型,它接受一个模型或者操作作为输入进行运算,并返回该操作产生的新模型。knossos.model内部代码如下:
1
(definterface+ Model
2
(step [model op]
3
"The job of a model is to *validate* that a sequence of operations
4
applied to it is consistent. Each invocation of (step model op)
5
returns a new state of the model, or, if the operation was
6
inconsistent with the model's state, returns a (knossos/inconsistent
7
msg). (reduce step model history) then validates that a particular
8
history is valid, and returns the final state of the model.
9
Models should be a pure, deterministic function of their state and an
10
operation's :f and :value."))
Copied!
结果发现Knossos检查器为锁和寄存器等东西定义了一些常见的模型。下面的内容是一个cas寄存器--正是我们需要建模的数据类型
1
(defrecord CASRegister [value]
2
Model
3
(step [r op]
4
(condp = (:f op)
5
:write (CASRegister. (:value op))
6
:cas (let [[cur new] (:value op)]
7
(if (= cur value)
8
(CASRegister. new)
9
(inconsistent (str "can't CAS " value " from " cur
10
" to " new))))
11
:read (if (or (nil? (:value op))
12
(= value (:value op)))
13
r
14
(inconsistent (str "can't read " (:value op)
15
" from register " value))))))
Copied!
只要knossos为我们正在检测的组件提供了模型,我们就不需要在测试中写cas寄存器。这只是为了你可以看到表面上一切顺利,其实是依靠底层怎么运行的。
此defrecord定义了一个名为CASRegister的新的数据类型,它拥有唯一不变的字段,名为value。它实现了我们之前讨论的Model接口,它的step函数接收当前寄存器r和操作op作为参数。当我们需要写入新值时,只需要简单返回一个已经赋值的CASRegister。为了对两个值进行cas,我们在操作中将当前值和新值分开,如果当前值和新值相匹配,则构建一个带有新值的寄存器。如果它们不匹配,则返回带有inconsistent的特定的模型类型,它表明上一操作不能应用于寄存器。读操作也是类似,除了我们始终允许读取到nil这一点。这允许我们有从未返回过的读操作历史。
为了分析历史操作,我们需要为测试定义一个:checker,同时需要提供一个:model来指明系统应该如何运行。 checker/linearizable使用Knossos线性checker来验证每一个操作是否自动处于调用和返回之间的位。线性checker需要一个模型并指明一个特定的算法,然后在选项中将map传递给该算法。
1
(defn etcd-test
2
"Given an options map from the command line runner (e.g. :nodes, :ssh,
3
:concurrency ...), constructs a test map."
4
[opts]
5
(merge tests/noop-test
6
opts
7
{:pure-generators true
8
:name "etcd"
9
:os debian/os
10
:db (db "v3.1.5")
11
:client (Client. nil)
12
:checker (checker/linearizable
13
{:model (model/cas-register)
14
:algorithm :linear})
15
:generator (->> (gen/mix [r w cas])
16
(gen/stagger 1)
17
(gen/nemesis nil)
18
(gen/time-limit 15))}))
Copied!
运行测试,我们可以验证checker的结果:
1
$ lein run test
2
...
3
INFO [2019-04-17 17:38:16,855] jepsen worker 0 - jepsen.util 0 :invoke :write 1
4
INFO [2019-04-17 17:38:16,861] jepsen worker 0 - jepsen.util 0 :ok :write 1
5
...
6
INFO [2019-04-18 03:53:32,714] jepsen test runner - jepsen.core {:valid? true,
7
:configs
8
({:model #knossos.model.CASRegister{:value 3},
9
:last-op
10
{:process 1,
11
:type :ok,
12
:f :write,
13
:value 3,
14
:index 29,
15
:time 14105346871},
16
:pending []}),
17
:analyzer :linear,
18
:final-paths ()}
19
20
21
Everything looks good!(‘ー`)
Copied!
历史记录中最后的操作是write 1,可以确信,checker中的最终值也是1,该历史记录是线性一致的。

多checkers

checkers能够渲染多种类型的输出--包括数据结构、图像、或者可视化交互动画。例如:如果我们安装了gnuplot,Jepsen可以帮我们生成吞吐量和延迟图。让我们使用checker/compose来进行线性分析并生成性能图吧!
1
:checker (checker/compose
2
{:perf (checker/perf)
3
:linear (checker/linearizable {:model (model/cas-register)
4
:algorithm :linear})})
Copied!
1
$ lein run test
2
...
3
$ open store/latest/latency-raw.png
Copied!
我们也可以生成历史操作HTML可视化界面。我们来添加jepsen.checker.timeline命名空间吧!
1
(ns jepsen.etcdemo
2
(:require ...
3
[jepsen.checker.timeline :as timeline]
4
...))
Copied!
给checker添加测试:
1
:checker (checker/compose
2
{:perf (checker/perf)
3
:linear (checker/linearizable
4
{:model (model/cas-register)
5
:algorithm :linear})
6
:timeline (timeline/html)})
Copied!
现在我们可以绘制不同流程随时间变化执行的操作图,其中包括成功的、失败的以及崩溃的操作等等。
1
$ lein run test
2
...
3
$ open store/latest/timeline.html
Copied!
现在我们已经通过测试,接下来详述系统中的故障引入