ITPub博客

首页 > 架构设计 > 基础设施 > VDL:唯品会强一致、高可用、高性能分布式日志存储 (质量篇)

VDL:唯品会强一致、高可用、高性能分布式日志存储 (质量篇)

原创 基础设施 作者:唯技术订阅号 时间:2018-11-30 14:37:07 0 删除 编辑

这是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=`${fiu_driver} ${vdl0_ip} ${fiu_port} 'remote.sync.pipe' 'sh '${vdl_root}'/tools/fiu/fiu_agent/script/start_vdl.sh' '10000'`

6.# 检查vdl0是否正确

7.Expect_EQ $start_vdl0 "1"

8.

9.# 启动vdl1

10.local start_vdl1=`${fiu_driver} ${vdl1_ip} ${fiu_port} 'remote.sync.pipe' 'sh '${vdl_root}'/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.${vdl_root}/tools/fiu/fiu_agent/script/pmsg.sh 6000 5 t

21.

22.}

脚本中,start_vdl1命令中的fiu_driver是driver的路径,vdl0_ip是要启动的VDL的IP地址,fiu_port是agent的port,remote.sync.pipe是指要同步执行, xxxx/start_vdl.sh是指agent要执行的脚本,10000是指超时时间。

启动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<20;i++))

11.{

12.# 伪随机选取要注入的异常

13.local inject_node=$((i%3))

14.local inject_error_index=$(( i % error_array_count ))

15.                    

16.echo '--------注入错误-----------'

17.PrepareOneCondition ${vdl_ip_array[inject_node]} ${fiu_port}'sync.point'${store_interface_error_array[inject_error_index]}

18.

19.#下面是检查代码

20....

21.}

22.}

关键代码是:

1.PrepareOneCondition ${vdl_ip_array[inject_node]} ${fiu_port}'sync.point'${store_interface_error_array[inject_error_index]}

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.}

-后面几步: 与前面的第一、二步类似,例如"判断节点情况",则Driver发送'remote.sync.pipe'请求到agent,agent执行"检查VDL脚本",返回结果。

 

到目前为止,我们介绍了VDL如何使用异常测试对各种情况进行模拟。下面我们来讲讲如何校验节点间数据一致性。

节点数据一致性校验

判断VDL是否能正常服务,可以简单通过监控页面看到,但如何才能简单地确认集群的数据是一致的呢?VDL在压测环境下,三个副本,目前共生产2400多亿条数据,并且还在不断增长中,灰度环境两天就能产生15亿条数据,如何才能简单校验副本间数据都是一致的呢?

由于数据量太多,不可能对每条数据进行对比,且每个节点的log文件不相同,也不可以简单使用md5直接对比文件。对此我们自研了一个数据校验工具,校验节点间的数据一致性。使用的方案是连续checksum的方式对数据进行对比。

如图所示,三个节点均从某个index开始,计算连续的checksum,只要相同的index有相同的checksum,则认为三节点数据均一致。

在实际使用中,我们将每个节点的checksum数据按一定规则输出到日志中,我们只要校验最新的相同的index,若有相同的checksum,则在["计算的index", "校验的index"] 这个区间的数据均一致。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69900365/viewspace-2222265/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-11-28

  • 博文量
    7
  • 访问量
    6461