百亿次的锤炼 - 地狱模式的分布式系统测试
本文以近期开源的Dragonboat多组Raft库为例,介绍Dragonboat这样一个典型分布式系统是如何做测试的。Dragonboat以Go实现,能在普通硬件上提供每秒1000万次以上的强一致读写,它是目前github.com上速度最快的功能完整的多组Raft开源库。欢迎大家试用,并请点Star支持:Dragonboat
最大的误导
常看到有系统吹捧自己可靠的方法是说某大型活动用了它,或说是某某公司某内部项目用了,从而得出可靠的结论,生产环境俨然成了廉价公关软文口中的测试平台。其实众所周知,某活动全场意外当机重启的节点数少之又少,磁盘毁损一整年才2-4%,而故障性的网络分区在很多DevOps岗的整个职业生涯里也就遇到几次而已,以至于多年来各一线公司网络分区造成故障的故事收集起来也才写满几页纸。某活动扛住了或是某项目用了,这些完全不是软件可靠与否的充要条件。
事实其实是残酷的。曾阅读过国内排名前四的某司一个共识库,30分钟代码读下来找到多处数据丢失毁损的bug,实现则是很典型的那种打死也不肯写测试的全裸奔模式。对于软件,任何无法用代码来验证的廉价营销式说辞,不说、不看、不听:
相较于廉价的文宣,Dragonboat对系统正确性满心敬畏,老老实实以完备的测试方案、开源的测试代码、公开可验证的测试结果数据三者来提供切实的保障。
常规测试
常规测试部分,Dragonboat做了:
- 数万行全手写测试代码,Raft协议3000行核心代码拥有过万行测试代码护航
- Go内建的race detector测试
- 各类静态检查,及早发现如错误返回值未处理等可能问题
- 使用go-fuzz对所有网络与本地输入做随机输入fuzz测试
Dragonboat内各Package的测试覆盖率基本均在90%以上。以Raft协议实现来看,不正确地删改一行代码基本就能触发多个测试错误。这些测试代码,连同下面要介绍的monkey testing,nightly build时在race detector被打开的情况下运行。对于长期被人诟病的Golang的error处理方式,的确容易因为人为疏忽造成error返回有漏检的可能,但仅gometalinter一个软件收录的静态检测工具就有多种能对付它。对各输入的Fuzz testing初听来或许有些多此一举,但一跑Fuzz testing几十秒就发现bug的例子比比皆是,Dragonboat开发中也曾遇到。
基于Raft协议的自测
Raft协议对内部数据有严格限定要求,比如显而易见的就有:
- 所有entry的Index值始终应该是连续且严格递增的
- 所有entry的Term值应该是单向的
- 当Index与Term确定时,entry内容是唯一确定的
这些都提供较小代价下运行时自测的机会。仅以第一项为例,在Dragonboat中它被落实到多个点位上,对应用透明的进行自测:
- 在节点完成了协议规定的检查,即将append log时
- 在entry被commit以后,准备由Raft协议返回供复制状态机执行时
- 复制状态机即将执行entry,由该entry Index对比当前最新已执行的entry的Index值时
这些自测在Dragonboat中无法通过任何设置予以关闭,甚至在Benchmark跑分时也严格限定必须进行。
磁盘文件IO测试
磁盘文件IO要做正确有多难,可以先看两个事实:
假设文件系统的可靠是天经地义的吧?很遗憾,这种假设也是高危动作。
TS Pillai的这张总结图表直观显示文件IO做正确有多难。
为解决这些磁盘文件IO的挑战,Dragonboat首先选择不自作聪明的到处自己去做文件操作,把存储尽可能交给RocksDB,并对基于RocksDB的系统加以各类测试:
- 使用ScyllaDB的charybdefs实现的磁盘错误注入测试
- 使用自动开关的掉电数据完整性测试
前者可以模拟诸如RocksDB试图读一个sst文件的内容时第二次读操作返回错误,帮助检查Dragonboat是否按照设计正确地处理这样的IO错误。掉电测试检查fsync是否被正确配置(如MacOS上是否fcntl(fd, F_FULLFSYNC)了)与调用,是否IO逻辑上有丢数据的问题。
看了上述介绍,可能有人觉得这是小题大做,从Turbo C就开始玩的文件操作有啥难?hehe,文件操作方面是git、ZooKeeper的作者经验多,还是您更牛?
as we know, there are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns—the ones we don't know we don't know.
Donald Rumsfeld
Monkey Testing
Monkey Testing有时也称为Chaos Engineering,目的在于自动测试系统在各组件失效当机情况下系统是否依旧能按设计提供应有的服务。与Fuzz testing的随机数据输入不同,Monkey Testing / Chaos Engineering着眼于随机破坏性事件对系统的影响。
在Dragonboat的monkey testing中,各种随机破坏性事件的组合被注入到一个多节点的测试环境里,在一年多的自动测试期间,导致了百亿数量级次数的Raft节点重启事件,发现并修正了大量Raft协议实现与相关辅助功能的bug。整个测试流程及其耗时,堪称地狱模式。具体的,在monkey testing中,被注入的随机事件有:
- 随意的停止各节点
- 随意删除节点所有Raft数据
- 随意丢弃传输中的消息
- 随意网络分割节点暂时阻断通讯
在上述大量注入的随机破坏性事件前提下,同时在上述多节点测试环境上运行大量Raft组实例,进行Raft的读写测试。该monkey testing环境同时内建一组三个节点的Drummer系统,三个Drummer节点观测、维护各Raft组健康信息,并在发现Raft组的成员失效以后,试图在其它节点上通过Raft组成员变更,新增并启动一个新的Raft成员,替换已失效的Raft成员。
上述三节点的Drummer本身也是一个基于Dragonboat的Raft实现的无单点系统,且在monkey testing中同样会被注入上述随机错误。Drummer的上述监控、修复Raft组的业务逻辑是在自身同样面对大量被注入的随机破坏性事件的前提下完成的,这进一步验证了此类具体实际业务逻辑下,Dragonboat的Raft实现的可靠性。
在一个节点平均存活仅几分钟的情况下,在几台服务器上每晚便可完成千万次量级的节点随机失效与重启测试。在此及其严酷的测试环境中,同时向系统施加Raft读写请求,配合大量后台的Raft快照保存与快照恢复操作,严格的后验检查确保:
- Jepsen的Knossos和porcupine检查,绝无违反称为linearizability的强一致性
- Raft组在有Quorum的时候需可用
- 用户应用状态机状态一致
- Raft组成员一致
- 磁盘上保存的Raft Entry Log一致
一部分Jepsen可读格式的edn log已被公布,可供大家使用各自选定的Linearizability checker检验:https://github.com/lni/knossos-data
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
开源 UI 库中,唯一同时实现了大表格虚拟化和树表格的 Table 组件
背景 有这样一个需求,一位 React Suite(以下简称 rsuite)的用户,他需要一个 Table 组件能够像 Jira Portfolio 一样,支持树形数据,同时需要支持大数据渲染。 截止到目前(2019年1月18日)为止,开源 UI 库中没有找到可以支持的组件,所以 rsuite 在最新的版本中支持这一特性。 接下来,我们看一下 rsuite 中是怎么支持这两个功能? 大表格虚拟化 首先,我们看一下支持大数据渲染,在页面中渲染过多的 DOM 元素会带来性能问题,必须得有一种解决方案去优化它,我们暂且叫做大表格虚拟化。 所谓的大表格虚拟化,其实就是为表格设置一个较大的数据(比如 10000 条数据),然后虚拟一个表格隐藏掉不需要显示的数据。 为了解决让浏览器渲染的大量 DOM 时候出现的性能问题,我们不能把 10000 条数据都渲染到页面,采用一种方式,只渲染可视范围内数据。 同时为表格设置一个滚动条,只有在滚动到需要显示的区域时候才渲染该区域的数据,减少的 DOM 数量。 预览地址 以上这是一个 10000 条数据的 Table,渲染后的 HTML 结构是: 我们可以看到...
- 下一篇
对Docker了解多少?10分钟带你从入门操作到实战上手
Docker简述 Docker是一种OS虚拟化技术,是一个开源的应用容器引擎。它可以让开发者将应用打包到一个可移植的容器中,并且该容器可以运行在几乎所有linux系统中(Windows10目前也原生支持,Win10前需要内置虚拟机),正所谓“一次打包,到处运行”。 Docker容器的运行是完全的沙箱机制,相互之间不会有任何关联(除非自己串联集群)。网络、存储、进程等资源,不仅对于不同的容器是相互隔离,对于宿主机和容器直接也是隔离的,除非你手动映射暴露端口或者挂载存储卷。 很多人不理解,Docker和虚拟机到底有什么区别。 从这两张结构图来看,Docker比虚拟机少了一层虚拟机操作系统,Docker的应用直接Docker引擎上运行。由于虚拟机需要一层操作系统,所以会导致虚拟机的体积非常大,通常在几G到十几G之间。并且通常一个虚拟机上,不只一个应用,因此对于整体的虚拟集群管理并不太友好,比较难做到灵活分配。而一个Docker镜像的体积大约在几十M到几百M之间,一般一个镜像只打包一个应用,由多个镜像组成一个完整的项目,并且镜像易于复制,可以跨平台运行,这样可以使项目的部署管理有更好的灵活...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS7,CentOS8安装Elasticsearch6.8.6