做者:ZStack 王为算法
从 2015 年到如今,ZStack 有一条宗旨一直没有变过,就是向客户交付稳定、可靠、高性能的云平台,这条宗旨在前几年让咱们一直聚焦云平台自己,包括虚拟化、云网络、云编排、存储管理等等这些功能。数据库
在这里面最让咱们头痛的,即便不是第一也能进前三的存在,就是存储管理。编程
考虑到存储对业务的无比的重要性,以及咱们做为一家创业公司的支持能力,咱们一开始一直是基于一些开源的存储方案对客户提供服务:缓存
综合考虑前面的各类存储,NFS、OCFS2 的不完美促使咱们提供一个可以管理共享存储的存储方案,这个方案要能达到下面的要求:安全
最终,在2018 年咱们决定本身开发一个面向共享块存储的存储方法,命名很直接就叫 SharedBlock。整个方案是这样的:服务器
SharedBlock 在推出后,应用在了不少的生产客户上,特别是能够利旧 SAN 存储特色让 SharedBlock 快速部署在大量以往使用虚拟化的客户上。网络
后来随着 5G 和物联网、云端互联的发展,让市场迫切须要一个价格不高、能够简便部署、软硬一体的超融合产品,所以咱们就在考虑一个两节点一体机的产品,经过和硬件厂商合做设计,能够实现 2U 的一体机包含足够用户使用的硬盘、独立的模块和双电冗余,咱们但愿能经过这个产品将客户的本来单节点运行的应用平滑升级到两节点备份,让客户的运行在轨道站点、制造业工厂这些“端”应用既享受到云的便利,又不须要复杂的运维和部署。这就是咱们的 Mini Storage。session
在开发这些存储产品的过程当中,咱们踩了无数的坑,也收获了不少经验。架构
下面先说说将存储作正确有多难,在今年说这个话题有一个热点事件是避不开的,就是今年的 FOSDEM 19' 上 PostgreSQL 的开发者在会上介绍了 PostgreSQL 开发者发现本身使用 fsync() 调用存在一个十年的 bug——并发
这样 PG 可能误觉得数据已经同步而移动了 journal 的指针,实际上数据并无同步到磁盘,若是磁盘持续没有修复且忽然丢失内存数据就会存在数据丢失的状况。
在这场 session 上 PG 的开发者吐槽了 kernel 开发以及存储开发里的不少问题,不少时候 PG 只是想更好地实现数据库,但却发现常常要为 SAN/NFS 这些存储操心,还要为内核的未文档的行为买单。
这里说到 NFS,不得很少提两句,在 Google 上搜索 "nfs bug" 能够看到五百万个结果,其中不乏 Gitlab 之类的知名厂商踩坑,也不乏 Redhat 之类的操做系统尝试提供遇到 NFS 问题的建议:
从咱们一个云厂商的角度看来,虚拟机存储使用 NFS 遇到的问题包括但不限于这几个:
最终咱们的建议就是生产环境、较大的集群的状况下,最起码,少用 NFS 4.0 之前的版本……
另外一个出名的文章是发表在 14 年 OSDI 的这篇 All File Systems Are Not Created Equal,做者测试了数个文件系统和文件应用,在大量系统中找到了不乏丢数据的 Bug, 在此以后诸如 FSE'16 的 Crash consistency validation made easy 又找到了 gmake、atom 等软件的各类丢数据或致使结果不正确的问题:
上面咱们举了不少软件、文件系统的例子,这些都是一些单点问题或者局部问题,若是放在云平台的存储系统上的话,复杂度就会更高:
1. 首先,私有云面临的是一个离散碎片的环境,咱们都知道 Android 开发者每每有比 iOS 开发者有更高的适配成本,这个和私有云是相似的,由于客户有:
1)不一样厂商的设备
2)不一样的多路径软件
3)不一样的服务器硬件、HBA 卡;
虽然 SCSI 指令是通用的,但实际上对 IO 出错、路径切换、缓存使用这些问题上,不一样的存储+多路径+HBA 能够组成不一样的行为,是最容易出现难以调试的问题地方,例若有的存储配合特定 HBA 就会产生下面的 IO 曲线:
2. 因为咱们是产品化的私有云,产品化就意味着整套系统不多是托管运维,也不会提供驻场运维,这样就会明显受客户良莠不齐的运维环境和运维水平限制:
1)升级条件不一样,有的用户但愿一旦部署完就不再要升级不要动了,这就要求咱们发布的版本必定要是稳定可靠的,由于发出去可能就没有升级的机会了,这点和互联网场景有明显的区别;
2)联网条件不一样,通常来讲,来自生产环境的数据和日志是相当重要的,但对产品化的厂商来讲,这些数据倒是弥足珍贵,由于有的客户机房不只不容许链接外网,甚至咱们的客户工程师进机房的时候手机也不容许携带;
3)运维水平不一样,对于一个平台系统,若是运维水平不一样,那么能发挥的做用也是不一样的,好比一样是硬件故障,对于运维水平高的客户团队可能很快可以确认问题并找硬件厂商解决,而有的客户就须要咱们先帮忙定位分析问题甚至帮助和硬件厂商交涉,就须要消耗咱们不少精力。
3. 漫长的存储路径,对于平台来讲,咱们不只要操心 IO 路径——Device Mapper、多路径、SCSI、HBA 这些,还要操心虚拟化的部分——virtio 驱动、virtio-scsi、qcow2…… 还要操心存储的控制平面——快照、热迁移、存储迁移、备份…… 不少存储的正确性验证只涉及选举、IO 这部分,而对存储管理并无作足够的关注,而根据咱们的经验,控制面板一旦有 Bug,破坏力可能比数据面更大。
说了这么多难处,咱们来讲说怎么解决。提到存储的正确性,接触过度布式系统的同窗可能会说 TLA+,咱们先对不熟悉 TLA+ 的同窗简单介绍下 TLA+。
2002 Lamport 写了一本书《Specifying Systems》基本上算是 TLA+ 比较正式的第一本书,了解的朋友可能知道在此以前 Lamport 在分布式系统和计算结科学就很出名了——LaTex、Lamport clock、PAXOS 等等,TLA+ 刚开始的时候没有特别受重视,他的出名是来自 AWS 15 年发表在 ACM 会刊的《How Amazon Web Services Uses Formal Methods》。
从本质上讲,形式化验证并非新东西,大概在上世纪就有了相关的概念,TLA+ 的优点在于它特别适合验证分布式系统的算法设计。由于对于一个可验证的算法来讲,核心是将系统时刻的状态肯定化,并肯定状态变化的条件和结果,这样 TLA+ 能够经过穷举+剪枝检查当有并发操做时会不会有违反要求(TLA+ 称之为 invariant)的地方——例如帐户余额小于 0,系统中存在了多个 leader 等等。
看最近的几场 TLA Community Meeting,能够看到 Elasticserach、MongoDB 都有应用。
那么既然这个东西这么好,为何在国内开发界彷佛并无特别流行呢?咱们在内部也尝试应用了一段时间,在 Mini Storage 上作了一些验证,感受若是 TLA+ 想应用更普遍的话,可能仍是有几个问题须要优化:
固然了,涉及到算法的正确性证实,形式化证实依然是不可替代的,但不得不说目前阶段在云平台存储上应用,还没作到所有覆盖,固然了咱们也看到 TLA+ 也在不断进步——
这里特别是第三点,若是咱们的 Spec 可以被转换成代码,那么咱们就能够将核心代码的算法部分抽象出来,作成一个单独的库,直接使用被 Spec 证实过的代码。
分布式系统的测试和验证,这几年还有一个很热门的词汇,就是混沌工程。
混沌工程对大多数人来讲并非一个新鲜词汇,能够说它是在单机应用转向集群应用,面向系统编程转向到面向服务编程的必然结果,咱们已经看到不少互联网应用声称在混沌工程的帮助下提升了系统的稳定性如何如何,那么对于基础架构软件呢?
在必定程度上能够说 ZStack 很早就开始在用混沌工程的思想测试系统的稳定性,首先咱们有三个关键性的外部总体测试:
上面这些方法,在大量调用 API、测试 IO 以外,很重要的一点就是注入错误,例如强制关闭虚拟机、物理机,经过可编程 PDU 模拟断电等等,可是这些方法有一些缺陷:
总之大部分混沌工程所提供的手段(随机关闭节点、随机杀进程、经过 tc 增长延时和 iproute二、iptables 改变网络等等)并不能知足 ZStack 的彻底模拟用户场景的需求。
在这种状况下,咱们将扩展手段放在了几个方向上:
使用 fiurun + fiuctl 能够对某个应用在须要的时刻控制系统调用。
fiu 对注入 libaio 没有直接提供支持,但好在 fio 扩展和编译都极为简单,所以咱们能够轻松的根据本身的需求增长 module。
2. systemtap,systemtap 是系统界的经典利器了,能够对内核函数的返回值根据需求进行修改,对内核理解很清晰的话,systemtap 会很好用,若是是对存储进行错误注入,能够重点搜 scsi 相关的函数,以及参考这里:Kernel Fault injection framework using SystemTap
3. device-mapper,device-mapper 提供了 dm-flakey、dm-dust、dm-delay,固然你也能够写本身的 target,而后能够搭配 lio 等工具就能够模拟一个 faulty 的共享存储,得益于 device-mapper 的动态加载,咱们能够动态的修改 target 和参数,从而更真实的模拟用户场景下的状态;
4. nbd,nbd 的 plugin 机制很是便捷,咱们能够利用这一点来修改每一个 IO 的行为,从而实现出一些特殊的 IO pattern,举例来讲,咱们就用 nbd 模拟过用户的顺序写很快但随机写异常慢的存储设备;
5. 此外,还有 scsi_debug 等 debug 工具,但这些比较面向特定问题,就不细说了。
上面两张图对这些错误注入手段作了一些总结,从系统角度来看,若是咱们在设计阶段可以验证算法的正确性,在开发时注意开发可测试的代码,经过海量测试和错误注入将路径完整覆盖,对遇到的各类 IO 异常经过测试 case 固化下来,咱们的存储系统必定会是愈来愈稳定,持续的走在“正确”的道路上的。