这是VDL系列的第三篇,本文主要讲述VDL在质量控制上的一些工作。
FIU测试
分布式系统,在正常情况下还是比较简单的。异常情况才是分布式系统的难点,包括节点故障、磁盘异常、网络延时/丢包、网络分区等等。如何在这些异常或者异常组合的情况下保证VDL稳定正常,这是VDL的重难点之一。
FIU是Fault Injection Utility的缩写,是我们为了更好地测试分布式系统(RDP/VDL)自研的一个小工具。FIU的主要目的是:自动化构造各种异常/错误,并且具备定制扩展的能力。
VDL使用FIU来模拟上述各种异常,并测试在异常情况下,VDL是否正常(表现是否符合预期)。我们先来简单介绍一下FIU框架。
FIU框架
如上图所示,FIU由三个组件组成:
-fiu_driver:driver是总控节点,是所有test case的发起端,为每一个test case准备所需的异常/错误等前提条件,然后执行设计的test case; fiu_driver和fiu_agent之间通过UDP协议通讯;
-fiu_agent:和测试的目标进程运行在相同机器上,负责接受fiu_driver的指令并在目标测试的机器上进行各种操作,完成这些操作一般通过编写shell脚本完成;FIU本身提供一些shell原型,用户开发的shell原型会自动同步到fiu_driver端,所以测试行为可以灵活扩展;fiu_agent和fiu_engine之间通过named shared memory同步fiu_driver开启的sync point;
-fiu_engine:以源代码和so两种方式嵌入到被测试的项目中,主要是插入各种sync point 和各种定点异常。
目前VDL的异常用例有30多例,还在不断地添加中,主要分类有:
1.模拟磁盘IO异常:包括磁盘写入异常、写入延时;
2.模拟网络异常:包括丢包,Delay,使用TC进行模拟。我们并不处理拜占庭的情况;
3.进程异常:包括随机Kill/Kill -9 VDL节点;
4.Raft流程异常:包括Delete Raft Log,落后节点加入集群,不断停止Leader节点等情况;
5.Log Store异常:包括数据损坏、索引文件异常等情况;
6.客户端异常测试:包括客户端边界请求、异常请求测试。
FIU的例子:
我们举一个例子方便与更加直观理解。假设我们有一个三节点组成的VDL集群,配合FIU,部署图如下:
(FIU Driver也可以跟agent机器部署在一起)
上图中fiu_driver用于给agent发送执行请求,异常测试用例脚本会部署在driver所在服务器,在执行用例过程中,脚本会调用fiu_driver给agent发送命令。fiu_driver不是常驻进程。
fiu_agent接收fiu_driver的请求,执行操作。主要有执行shell脚本与往Share Mem Map 注入异常信息。fiu_agent是常驻进程。
假设我们要完成如下的异常测试:
1.#####################################
2.#
3.# 流程:
4.#
5.# 测试与Raft流程相关的存储接口异常时,VDL集群情况
6.# 1) 启动3节点集群,启动发送服务
7.# 2) 选取一个节点,往存储接口注入错误
8.# 3) 判断节点情况
9.# 4) 恢复节点,清除错误
10.# 5) 循环2-4,向下一个接口注入错误
11.# 6) 共测试20次
12.# 7) 完成20次异常后,清除错误,关闭发送服务,并检查
13.#
14.# 预期:
15.# 1) 注入错误后,节点会crash
16.# 2) 集群恢复正常后, 集群有Leader, 且三节点raft log数据一致
17.#
18.# ###################################
那么需要实现一个测试用例,并放在driver所在的机器上:
1.function TestCase_logstore_error() {
2.// 过程先略过
3.}
我们按用例的流程介绍:
-第一步: 启动3节点集群,启动发送服务
1.function TestCase_logstore_error() {
2.
3....
4.# 启动vdl0
5.local start_vdl0=`$ $ $ 'remote.sync.pipe' 'sh '$'/tools/fiu/fiu_agent/script/start_vdl.sh' '10000'`
6.# 检查vdl0是否正确
7.Expect_EQ $start_vdl0 "1"
8.
9.# 启动vdl1
10.local start_vdl1=`$ $ $ 'remote.sync.pipe' 'sh '$'/tools/fiu/fiu_agent/script/start_vdl.sh' '10000'`
11.# 检查vdl1是否正确
12.Expect_EQ $start_vdl1 "1"
13.
14.# 启动vdl2
15....
16.
17.# 启动发送服务
18.# 启动发送服务会执行本地的一个shell脚本,包括动态编译producer并启动,不再详细展开。
19.echo '-------------produce message async(start)----------------'
20.$/tools/fiu/fiu_agent/script/pmsg.sh 6000 5 t
21.
22.}
启动VDL,测试脚本会调用driver,往agent发送启动命令。agent接受到请求后,执行本地脚本并返回结果,流程图如下:
-第二步: 选取一个节点,往存储接口注入错误
1.function TestCase_logstore_error() {
2.
3.......
4.
5. #这里定义要注入的错误的数组
6.local store_interface_error_array=('fiu_logstore_entries_error' 'fiu_logstore_term_error' ...省略)
7.local error_array_count=${#store_interface_error_array[@]}
8.
9.#------节点注入错误--------------------------------
10.for((i=0;i
11.{
12.# 伪随机选取要注入的异常
13.local inject_node=$((i%3))
14.local inject_error_index=$(( i % error_array_count ))
15.
16.echo '--------注入错误-----------'
17.PrepareOneCondition $ $'sync.point'$
18.
19.#下面是检查代码
20....
21.}
22.}
关键代码是:
1.PrepareOneCondition $ $'sync.point'$
PrepareOneCondition是共用函数,实际是调用driver,往agent执行'sync.point'命令。流程如下:
往Share Mem Map插入Key后,VDL使用fiu_engine的接口,就能获取到对应的错误信号,代码如下:
1. // 下面是Go代码
2.func (s *LogStore) Term(i uint64) (uint64, error) {
3.
4.// mock error
5.if fiu.IsSyncPointExist(vdlfiu.FiuStoreTermError) {
6.return 0, errors.New("Mock FiuStoreTermError")
7.}
8.}
到目前为止,我们介绍了VDL如何使用异常测试对各种情况进行模拟。下面我们来讲讲如何校验节点间数据一致性。
节点数据一致性校验
判断VDL是否能正常服务,可以简单通过监控页面看到,但如何才能简单地确认集群的数据是一致的呢?VDL在压测环境下,三个副本,目前共生产2400多亿条数据,并且还在不断增长中,灰度环境两天就能产生15亿条数据,如何才能简单校验副本间数据都是一致的呢?
由于数据量太多,不可能对每条数据进行对比,且每个节点的log文件不相同,也不可以简单使用md5直接对比文件。对此我们自研了一个数据校验工具,校验节点间的数据一致性。使用的方案是连续checksum的方式对数据进行对比。
如图所示,三个节点均从某个index开始,计算连续的checksum,只要相同的index有相同的checksum,则认为三节点数据均一致。
在实际使用中,我们将每个节点的checksum数据按一定规则输出到日志中,我们只要校验最新的相同的index,若有相同的checksum,则在["计算的index", "校验的index"] 这个区间的数据均一致。
领取专属 10元无门槛券
私享最新 技术干货