小米开源分布式KV存储系统Pegasus( 五 )


面对这样一个复杂的系统,任何一个异常的数据库状态都会使我们抓耳挠腮,比如 Alice 和 Bob 的账户信息的如下变迁:
(, ) -> (, )
程序没有 bug 时,我们可以假定 Alice 和 Bob 账户的余额之和是等于 200 的 。现在余额和变成了 210,一定是某个环节出了问题 。也许是两人同时向对方转账时,然后触发了什么 bug;也许是数据包被发送了两次 。进入非法状态的可能情况有很多种,但硬件概率性的失败以及 Alice、Bob 间复杂 (可能是并行) 的转账记录会使得我们不太容易把产生非法状态的原因定位并复现出来 。
而对这样的一个问题,我们的武器是模拟和伪随机 。通过这两种手段,我们希望程序能按着可复现的事件序列运行 。这样假如程序因为进入非法状态而 crash,我们可以对该状态进行复现、调试和回归 。
还拿上面 Alice 和 Bob 的交易为例 。一个双方同时转账的流程在模拟的环境里可能是这样运行的:Alice 发起转账 –> Bob 发起转账 -> Alice 发起写盘 -> Alice 发起 RPC -> Alice RPC 成功 -> Bob 发起写盘 -> Alice 写盘失败
换句话说:
假设能有这样的一个环境,分布式业务逻辑的 debug 难度就一定会降很多 。一方面,随机的存在使得我们有测试各种执行序列的能力;另一方面,伪随机和错误注入,使得我们可以复现遇到的问题 。
控制不确定性
那么,具体的控制不确定是怎么做的呢?
产生不确定性的接口提供抽象层 。当前系统一共提供了线程池、RPC、磁盘 IO、线程同步和环境操作 5 种抽象接口 。每种接口都有模拟实现 (测试) 和非模拟实现 (部署运行),分布式业务逻辑 ( Logic) 的代码只调用系统的抽象接口 ()
系统提供能在单进程中模仿多个服务节点的能力,从而达到模拟分布式系统的效果 。
层的模拟实现是一个难点,重点包括:
只提供纯异步的编程接口,节点的运行模式就是线程执行事件队列中任务的过程 。
当业务逻辑通过API 调用到的模块内部时,会将当前节点挂起,并按照伪随机的方式选取一个新的节点进行执行 。整个过程请类比单核多任务的操作系统,就像内核态,分布式逻辑的 Node 就像用户态进程,API 就像系统调用 。如下图所示:
对于提交到事件队列中的 IO 事件,我们按照一定的概率伪随机的注入错误 。另外,引入了一些全局的状态检测模块,在每次状态变化之时检测全局状态是否合法 (比如检查 Alice 和 Bob 的账户余额之和是不是小于 200);同时,Logic 的代码也加入了很多的点来检查 Node 状态是否合法 。程序 crash 后,就可以用相同的随机数种子进行复现和调试 。如下图所示:
的单元测试也是利用这套测试框架进行的 。在单元测试中,我们会用一个脚本来描述为一个场景施加的动作以及预期的状态,上图的 App Logic的功能就是加载脚本,然后按照脚本的方式检测程序状态是否符合预期 。
所采用的这套测试框架,其理念和实现均来源于微软的开源框架 rDSN 。整个框架较难以理解,这里也只是简述下其原理 。大家如果感兴趣可以直接查看源代码 。
现状和计划
现在在小米内部已经稳定服务将近一年左右,服务了近十个业务 。有关的存储引擎、性能以及设计方面的更多阐述,大家可以移步 Arch2016 的另一次分享(文后有链接),也可以参考我们在上的相关文档 。
后续我们会把数据的冷热备份、 分裂等功能都开源出来,请持续保持关注 。也欢迎各路人士加入小米,加入我们团队 。
写在最后
最后概括一下全篇的内容 。
【小米开源分布式KV存储系统Pegasus】