自己拼凑的一个分布式存储服务

  现代互联网的发展对服务质量的要求越来越高,后台开发中对基础组件和服务的可靠性要求也越来越严格,在这类需求下通过多副本的分布式协议提高服务整体可靠性也越来越被证实为行之有效的解决手段。虽然Lamport早在上个世纪就提出了Paxos算法,但一直被觉得因为难以理解,所以工业上基本也就几个技术实力雄厚的大厂将其实现并应用在生产环境中,不过随着Stanford的Diego Ongaro提出Raft一致性算法后,因为其在Paxos的基础上做出了很多的假设和约束,使得该协议更容易被理解和工程实现,因此近年来基于Raft实现的分布式系统突如百花齐放,使人感觉上高端无比的分布式系统也可以被我们这技术菜鸟们学习、研究甚至实现了。
  之前通读过不少分布式系统的设计和实现论文,其实可以发现处于稳定状态的分布式系统工作原理很简单明了,通常整个论文的绝大部分则是用于解释和证明系统运行过程中各种异常、突发情况下的正确性,以及可以部署在工业级生产环境下一个可靠性强、便于实现和维护的分布式系统的各种细节性问题。其实在仔细研读作者的博士论文之后,同时参照一些开源实现代码,觉得自己实现Raft协议还是没啥问题的,不过对于分布式系统除了实现算法,后续正确性验证还需要更多的工作量去完成。这里我就投了一个巧:使用Diego Ongaro的logcabin这个样例项目工程,剥离出其中Raft协议之后,再将自己的业务代码添加进入,就可以快速得到一个具备分布式能力的服务了,而且我想作者自己操刀实现的Raft协议库应当比绝大多数人的实现都更加完整可靠吧。
  logcabin是基于Raft协议实现的一套驻留于内存、树状层次结构的存储系统,类似于UNIX文件系统中的目录和文件层次结构,通过自带工具可以实现目录和文件的操作管理。为了将这个项目折腾的更加好玩,于是在原作者项目基础上做了一些更改操作,并命名为RaftStore,立志将其实现为一个可靠的静态资源存储服务。当然折腾不是最终目的,自己的初衷就是在折腾中加深对Raft协议以及分布式系统的理解,能够在折腾中有所习得吧。


1. 使用CMake代替Scons,简化项目代码

  个人比较习惯使用CMake做项目管理,对Scons不是很熟悉,也不知道这个编译管理工具有什么独到过人之处,不过看到SConstruct文件觉得用起来也挺麻烦的。
  然后,我还将单元测试、模块测试的代码也都统统移除了,因为当前阶段是结合代码加深对Raft协议更深入的理解,所以精简代码可以排除干扰、加快学习进度。此处并不是否认单元测试、模块测试用例的价值之所在,到后续阶段会考虑再将这些测试用例添加到项目中,使用CMake再行统一管理。

2. 将基于内存的树状存储修改为基于LevelDB的扁平持久化存储

  其实不仅仅是logcabin里面所实现的Raft库,任何一个成熟通用的Raft框架应当只需要其目标用户改写状态机变更函数、快照的建立和加载模块,只有这些操作是和业务逻辑相关联的,其他的细节(包括日志的处理)都可以由通用的协议库全权负责处理。
  这里将原项目中Tree的结构描述、管理、访问的代码全部删除了,取而代之的是使用LevelDB支撑扁平高效的持久化存储,将LevelDB的常用操作接口进行封装实现。这里使用LevelDB而没有使用更加流行的RocksDB,主要是因为后者的东家facebook对C++新标准的使用比较激进,新版本的RocksDB甚至需要C++14的支持,而在老的RHEL 6.x平台上就只能使用LevelDB了。在功能上除了支持最常见的SET、GET、REMOVE操作外,还根据LevelDB的迭代器功能扩充支持了RANGE和SEARCH接口,以便实现键的遍历、搜索的功能。
  除了对状态机变更操作进行了改写之外,还对快照的创建和加载操作也做了对应的更改:创建快照的时候执行整个LevelDB库的遍历,然后将所有的键值对取出来后使用Protobuf进行序列化保存,因为LevelDB是支持快照功能的,所以任何时候开始的快照保存都能够保证数据是完整的;加载快照的过程就更简单了,先将整个LevelDB进行destroy,然后再从快照中依次反序列化数据并SET到LevelDB中就可以了。

3. 融合tzhttpd框架,生成一套HTTP的访问接口

  logcabin原始项目只提供了一个TreeOps可执行文件作为客户端执行各项操作,在本项目中也将TreeOps修改成了rsOps以支持上述LevelDB持久化存储的操作接口。
  除此之外,RaftStore还将之前的tzhttpd库添加到了项目中,为访问底层资源提供了一套标准RESTful风格的操作接口。与此同时还增强了tzhttpd以提供了Basic Auth的认证功能,并且在URI中嵌入数据库名来进行资源的相互隔离;提供了POST接口以支持对大容量、二进制的数据(比如图片)提供存储支持;支持RAW格式取数据,此时访问接口会自动根据KEY名的后缀添加对应的HTTP Application头部信息,这样就可以把RaftStore作为通用高可靠的静态对象存储服务,很容易就可以继承在其他Web服务中。
  我们在日常工作中会有很多的命令、密码、脚本等信息需要记录,之前都是把他们存储在云笔记当中的,每次使用的时候进行搜索显得比较麻烦,于是这里适配了客户端script脚本,用户可以将其添加到自己的环境中去,就可以更加方便的记录和搜索常用的碎片信息了。

  从logcabin的代码看来,作者Diego Ongaro代码风格还是很优秀的:风格统一、注释清晰丰富、单元测试和模块测试用例覆盖全面,是很值得大家去学习和效仿的样例工程。同时作者为了将其开发成一个依赖度最低的通用库(对编译完的可执行文件执行ldd就可以查看),所以徒手造了很多的轮子,后续可以根据自己的喜好和特长进行取舍替换。而且,这个项目还有一个学习点就是Protobuf结合Eventloop的使用,实质上也就是实现一个轻量级的RPC框架了。
  国庆除了在家带孩子,就折腾了这么个玩意儿;-_),只是为了好玩吧!当然要做一个真正意义上的分布式KV存储还有很长的路要走,比如:根据KEY进行分片以提高系统的性能,分区的自动分割和合并,Raft的日志模块也可以使用LevelDB进行更高效的存储等,后续就看这份热情最终能持续走多久吧!

本文完!