线性化测试入门

93 阅读3分钟

线性化测试

线性化测试是分布式测试的一种,它是一种测试强一致性 (也就是CP架构的软件) 的测试,在面对最终一致性时大概率会报错(也有可能欧皇附体,没有报错?)。

一般使用Jepsen进行测试,也可以用porcupine。前者提供了更加全面的测试环境(网络故障,分区,延时模拟,并发模拟等),后者更易上手。

什么是线性化

借用porcupine的图

image.png image.png

  • 图中有三个客户端,分别发送了一个请求。
  • 三个请求中有一个put方法,两个get方法
  • 蓝色方块的左端代表着这个操作的调用时间,右端代表着操作的结束时间
  • 竖线表示:线性化模拟,此操作是在那一瞬间完成的。
  • 在图二中,可能是因为不同节点的数据没有同步完,且读了从节点导致的

线性化测试流程

  1. 定义模型

    1. 初始化状态(状态是一个变量,一直维护的)

    2. 分区函数

    3. 检查这一步的正确性

    上面三个是模型最重要的三个参数,以etcd(raft算法的KV键值对中间件)为例子,

    1. etcd的值初始化为null
    2. etcd不同键的操作是分离的,应该区分开,分别检查
    3. etcd有三种操作 1:read; 2:write; 3:cas;
      1. read,检查读的数据是否和模型的状态一致
      2. write,将状态修改成write的值
      3. cas, 模拟cas ,成功修改状态
  2. 记录操作

    一般操作由下面几个属性组成

    1. 操作类型,比如上面etcd的1;2;3;
    2. 调用时间
    3. 调用参数
    4. 返回时间
    5. 返回值

    也可以将一个操作分成调用事件和返回事件

    一般操作随机生成,确保测试的准确性

  3. 验证操作

    交给Jepsen、porcupine 验证操作的线性化

线性化验证的原理

线性化验证正确性,并找出可能的非法操作,即找到最长有效调用链

下面我们来分析一下,以下面这张图为例子

image.png

有三个调用,按照调用顺序分别是 0:put 1:get 2:get

我们要找到最长有效调用链

一个操作有两种可能

  • 可线性化
  • 不可线性化

现在好像就变成了选与不选 假设init state='200'增加讨论的多样性。 合法用ok,不合法用error.

  1. 选0,state=200 ok
    1. 选1 get==state ok
      1. 选2 get!=state error
      2. 不选2
    2. 不选1
      1. 选2 get!=state error
      2. 不选2
  2. 不选0
    1. 选1 get==state ok
      1. 选0,state=200 ok
        1. 选2 get!=state error
      2. 不选0
        1. 选2 get!=state error
    2. 不选1
      1. 选2 get!=state error
      2. 不选2

所以能选最长的是'01'或者'10'(init state='200')

我们可以使用位运算标记哪些操作被选择了。然后用深搜+回溯搜出来。

非常暴力的解决办法,但是这个问题是NP-hard问题,没有什么特别好的解决办法。

  1. 如果一个call事件是合法的,将call和return事件标记,放入调用栈中,从头执行
  2. 如果call不合法,检查下一个事件
  3. 如果是return事件且没有被标记,意味着前面有一个不合法的call
    1. 从调用栈中弹出call,取消标记call和event,从call的下一个事件开始检查,尝试其他可能的路径。
    2. 如果弹不出来,线性化失败。