线性化测试
线性化测试是分布式测试的一种,它是一种测试强一致性 (也就是CP架构的软件) 的测试,在面对最终一致性时大概率会报错(也有可能欧皇附体,没有报错?)。
一般使用Jepsen进行测试,也可以用porcupine。前者提供了更加全面的测试环境(网络故障,分区,延时模拟,并发模拟等),后者更易上手。
什么是线性化
借用porcupine的图
- 图中有三个客户端,分别发送了一个请求。
- 三个请求中有一个put方法,两个get方法
- 蓝色方块的左端代表着这个操作的调用时间,右端代表着操作的结束时间
- 竖线表示:线性化模拟,此操作是在那一瞬间完成的。
- 在图二中,可能是因为不同节点的数据没有同步完,且读了从节点导致的
线性化测试流程
-
定义模型
-
初始化状态(状态是一个变量,一直维护的)
-
分区函数
-
检查这一步的正确性
上面三个是模型最重要的三个参数,以etcd(raft算法的KV键值对中间件)为例子,
- etcd的值初始化为null
- etcd不同键的操作是分离的,应该区分开,分别检查
- etcd有三种操作 1:read; 2:write; 3:cas;
- read,检查读的数据是否和模型的状态一致
- write,将状态修改成write的值
- cas, 模拟cas ,成功修改状态
-
-
记录操作
一般操作由下面几个属性组成
- 操作类型,比如上面etcd的1;2;3;
- 调用时间
- 调用参数
- 返回时间
- 返回值
也可以将一个操作分成调用事件和返回事件
一般操作随机生成,确保测试的准确性
-
验证操作
交给Jepsen、porcupine 验证操作的线性化
线性化验证的原理
线性化验证正确性,并找出可能的非法操作,即找到最长有效调用链
下面我们来分析一下,以下面这张图为例子
有三个调用,按照调用顺序分别是 0:put 1:get 2:get
我们要找到最长有效调用链
一个操作有两种可能
- 可线性化
- 不可线性化
现在好像就变成了选与不选 假设init state='200'增加讨论的多样性。 合法用ok,不合法用error.
- 选0,state=200 ok
- 选1 get==state ok
- 选2 get!=state error
- 不选2
- 不选1
- 选2 get!=state error
- 不选2
- 选1 get==state ok
- 不选0
- 选1 get==state ok
- 选0,state=200 ok
- 选2 get!=state error
- 不选0
- 选2 get!=state error
- 选0,state=200 ok
- 不选1
- 选2 get!=state error
- 不选2
- 选1 get==state ok
所以能选最长的是'01'或者'10'(init state='200')
我们可以使用位运算标记哪些操作被选择了。然后用深搜+回溯搜出来。
非常暴力的解决办法,但是这个问题是NP-hard问题,没有什么特别好的解决办法。
- 如果一个call事件是合法的,将call和return事件标记,放入调用栈中,从头执行
- 如果call不合法,检查下一个事件
- 如果是return事件且没有被标记,意味着前面有一个不合法的call
- 从调用栈中弹出call,取消标记call和event,从call的下一个事件开始检查,尝试其他可能的路径。
- 如果弹不出来,线性化失败。