最近有个新的测试需求,我们有一个基于 nacos server 封装的 proxy,我们的需求是需要保证配置中心推送数据之后保证推送的数据一定是最新的数据。所以需要验证数据推送是否能够保证一致性。
我们知道,nacos 配置中心推送流程是
所以整体来看,如果 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 逻辑