其他测试框架 数据一致性验证

唐潇唐 · 2023年10月02日 · 最后由 恒温 回复于 2023年10月03日 · 3743 次阅读

背景

最近有个新的测试需求,我们有一个基于 nacos server 封装的 proxy,我们的需求是需要保证配置中心推送数据之后保证推送的数据一定是最新的数据。所以需要验证数据推送是否能够保证一致性。

我们知道,nacos 配置中心推送流程是

  1. nacos server 更新到数据之后,数据更新 DB,数据返回 publish 成功,并且发送 event 通知其他 nacos 节点 publish 事件,告诉其他节点数据更新了;然后异步发送事件更新本地内存以及更新本地的磁盘缓存
  2. 如果存在客户端订阅该 key,首先会推送 key 的更新事件给客户端,客户端会基于该 key 查询该 key 对应 value 的 md5 是否更新,如果 md5 不一致,说明数据有更新,那么将最新的数据推送给客户端

所以整体来看,如果 nacos server 还没来得及刷新本地缓存,客户端查询到该 key 就是旧数据。所以,按照该流程是没法保证数据一致性的,我们的 proxy 具体处理逻辑不做具体说明,那么我们怎么验证数据一致性呢

数据一致性验证

我们知道,一般数据一致性验证绕不过的就是jepsen,但是 jepsen 的问题在于成本过高,除了 clojure 语言成本,还有每次都需要初始化一个 db,而我们的场景是基于一套现有的 nacos 集群以及 proxy 集群来进行验证,所以无法初始化 db 集群

除此之外,还有一个框架porcupine可以验证数据一致性,它的优势是使用 go 语言编写,学习成本更低,并且不需要初始化 db 集群,不过,读写组合逻辑以及 checker 逻辑都需要自己实现。因为 nacos 也有 go 的 SDK,所以 porcupine 能够完美满足我们的需求。

以下会写一个 demo 来验证 nacos server 的读写一致性验证,验证 1 个线程写,3 个线程读,是否能够强一致

首先需要生成线性一致性验证的 history

func generateLinearizaHistory(cs []config_client.IConfigClient) {
    var count atomic.Int64
    var wg sync.WaitGroup
    var id atomic.Int64
    for {
        count.Add(1)
        if doneFlag {
            break
        }
        success, err := pushConfig(strconv.Itoa(int(count.Load())), cs)
        if err == nil && success {
            wg.Add(3)
            //time.Sleep(time.Millisecond * 400)
            for i := 0; i < 3; i++ {
                go func(c int64, i int) {
                    content, err2 := getConfig(cs)
                    if err2 == nil {
                        atoi, err2 := strconv.Atoi(content)
                        if err2 != nil {
                            wg.Done()
                            return
                        }
                        add := id.Add(1)
                        history.wg.Add(1)
                        defer history.wg.Done()
                        history.Events = append(history.Events, porcupine.Event{
                            Kind:  porcupine.CallEvent,
                            Value: Request{Op: 1, Value: int(count.Load())},
                            Id:    int(add),
                        })
                        history.Events = append(history.Events, porcupine.Event{
                            Kind:  porcupine.ReturnEvent,
                            Value: Response{Value: atoi, Ok: true},
                            Id:    int(add),
                        })
                        fmt.Println(fmt.Sprintf("[req] %d [resp] %d", int(count.Load()), atoi))
                    }
                    wg.Done()
                }(count.Load(), i)
            }
            wg.Wait()
        }
    }
}

那么具体的 checker 验证也比较简单,只需要将 history 传递过去即可生成是否满足线性一致性需求

func CheckHistory(history []porcupine.Event) {
    //bytes, _ := json.Marshal(history)
    //fmt.Println(string(bytes))

    model := porcupine.Model{
        Init: func() interface{} { return "" },
        Step: func(state interface{}, input interface{}, output interface{}) (bool, interface{}) {
            req := input.(Request)
            res := output.(Response)

            if req.Op == 1 {
                if res.Ok && res.Value == req.Value {
                    return true, state
                }
                if !res.Ok {
                    return true, state
                }
                fmt.Println(fmt.Sprintf("fail [req] %d [resp] %d", req.Value, res.Value))
                return false, state
            }
            return false, state
        },
    }
    verbose, info := porcupine.CheckEventsVerbose(model, history, 10*time.Second)
    file, err := ioutil.TempFile("./", "*.html")
    if err != nil {
        panic(err)
    }
    err = porcupine.Visualize(model, info, file)
    if err != nil {
        panic(err)
    }
    fmt.Println(verbose)
}

以上是一个抛砖引玉,能够比较简单的来验证是否满足线性一致性需求。可以基于以上 demo 扩展比较复杂的 checker 逻辑

共收到 1 条回复 时间 点赞

有意思

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册