自制分布式数据库izanami

1. 前言

        会想着写izanami这么一个数据库,很大一部分原因是因为看见很多数据分析的同事老是自嘲道“只会group by”。以前做RDBMS的时候,遇到group by通常都会希望后面的列跟着索引,那样的话数据预先就是排好序的,比较方便。但是但多数数据库都是采用B-Tree这种数据结构来实现索引的,当内存中的数据写入磁盘的时候,会有大量的随机IO,一块普通的机械盘iops大概也就一两百的样子,很难说能够满足业务需求。刚好差不多弄了一年的HBase,对LSM树有比较深的了解,所以就想着能不能用LSM树,实现一个分布式存储系统,专门存索引数据,这样对olap分析也许会有帮助。

2.  关于LSM树

         其实说白了LSM树跟B-Tree没有什太大的区别,甚至可以说为了读取效率,LSM树的“梦想”就是变成一个B-Tree。虽然随机的内存IO虽然理论上来说会因为cache实效而导致效率偏低,但是由于它是电子设备还是要比只能达到几百iops的磁盘强很多。LSM树针对这种原理,将最近写入的可能产生随机IO的数据都放到内存内部,以一颗B-Tree或者跳跃表的形式维护,等数据量到达一定大小或者过了一段时间之后在flush到磁盘上形成一个文件,这样这个文件可以看成是一个序列化的B-Tree。随着时间推移,磁盘上的文件会越来越多,但是查询的时候,每个文件都可能有你想要的数据,再加上普通磁盘iops就那么点,所以读取效率就自然低了,为了解决这个问题最常见的两个做法就是布隆过滤器跟compact了,布隆过滤器是个治标不治本的办法,compact说白了就是将多个局部有序的存放在磁盘上的B-Tree重读重写一次,形成一颗单独的B-Tree,这样一来只会有一棵树,单次查询的iops就降下来了(虽然compact也可以删除多余数据,但是还是感觉降低iops是最重要的)。

         既然LSM树在oltp这种需要iops的系统中表现不佳,那么把它放到olap系统中怎么样呢?这样可以实时写入,而且olap系统可以借助系统的page/cache达到跟内存读差不了太多的效果。

3. 关于izanami

        izanami是本人以LSM树,结合索引的原理,写的一个分布式数据库。由于近一年来都在维护公司的HBase,所以这个数据库也融合了很多HBase的概念,比如使用非结构化的存储方式、最基本的数据模型都是一种叫做cell的数据结构。但是由于坚持“解决gc最好的办法是不用java”这种理念,所以是使用C语言编写的。


3.1 基础结构

         从某种角度来看,HBase是以rowkey/family/column/ts/type/mvcc/value为最基础的数据结构,构成的最结构化的行式数据库。而izanami则是以column/value/rowkey/mvcc为最基础的数据结构构成的列式数据库,而且两者同样都使用rowkey作为分区键,分区的名字也模仿HBase叫做region。其实izanami与orc这类列式存储不同,orc可以说是按照column/rowkey/value这样的方式进行存储,这样在列比较多的时候,就不用额外存储空间在每个列的部分都存储rowkey,相较于izanami省了不少存储空间;但是在本人看来izanami的存储方式也有自己的优势,值相近的数据都物理上连续在一起,其实这样分析的时候做分析就非常方便了,这样相当于自带了shuffle的功能,不需要额外的空间做分组聚合,oom的错误概率也会变小,而且这样存储数据倾斜的概率会小很多。


下面是个例子

原始表数据


izanami存储格式
orc存储格式


3.2关于数据冗余

        可以考虑使用压缩来改善izanami的存储冗余,HBase的存储原理其实跟izanami差不多,但是亲测使用snappy之后,空间普遍只有原来的五分之一,再者按照压缩的原理来说,izanami的压缩率可能比HBase更高,因为column、value、rowkey的相似度是依次递减的,压缩的原理又是用短的数据替代出现次数多的长的数据,按照izanami的存储原理,相似度最高的column更有可能存储到一起,所以理论上压缩率说不定更高(自己精力有限,暂时做不了)。

3.3关于compact

        正如前面所说,compact过程对LSM树系统尤为重要,但是其本质就是将所有选中文件重读重写了一次,带来的不小的读写放大,而且像HBase这种系统在文件数太多的情况下,compact速度还是不受限制的。虽然compact有很多不好的影响,但是为了读取、尤其是oltp的随机读取,不得不做。izanami的定位是解决olap问题,每次读取(起码在笔者的代码中)都会读取从选中文件中读取一个比较大的数据块(默认1M),可以利用操作系统的page/cache 实现缓冲,但是随着文件数量增加,每次读取任务对应的动态生成的reader(代码中一个动态生成的数据结构)也会变多,reader又是会在读取过程中不断排序的,所以理论上来说也应该有个compact操作来定时合并文件。可是compact的核心代码就是用户发出的读取请求加上文件的flush,再加上笔者精力有限,也没有实现。

3.4 关于delete与update

         现在笔者做的模型是不支持这两种操作的,虽然就笔者接触的业务中,这两种操作在分析系统中其实也不多,但是要支持也有办法的。在每个region内部维护另一个LSM树,当对应的region接收到某个rowkey的某个column的删除请求后,在这课LSM中添加一个类似column/rowkey/mvcc这样的数据,当客户端有读取请求的时候要保证读取的cell中的mvcc要大于这个额外LSM树中对应记录的mvcc。至于update则可以看成一次delete加上一次put操作。

         随着记录删除数据的LSM树引入,可能会额外多一种compact。当一个cell有多次删除的时候,在新增的LSM树中会有多条mvcc不同,但是rowkey跟column相同的数据,可以单独使用一种compact专门针对这种记录合并,只保存mvcc最大的cell。只有3.3中的compaction才可以彻底删除所有的这种删除记录。

3.5 关于非结构化

        之所以将izanami设计为非结构,是基于以下这种考虑,比如现在有个地理位置的列需要分析,但是这种数据又是每天都有的,业务有需要按天分析,那么可以考虑设计location20180101、location20180102这两个列,分别表示两天上报的数据。而且如果业务需要分析所有location数据的话,这些数据又是相邻的。

3.6 izanami的缺点跟不规范的地方

        izanami也有很多缺点:

        1)对于oltp类型的读取,izanami是完全没能派上用场的;

        2)为了而且方便最后内存中的数据写入文件,笔者没有写成一颗B-Tree,而是一个有序的文件;

        3)关于izanami在分区容错性,由于笔者能力有限,也采用了最简单最粗暴的“CA”型方式。


4. 关于CAP的一些个人理解

4.1 一些CAP的说法

        CAP理论在分布式存储系统中似乎已经成为了一种共识,但是笔者认为有些关于CAP的说法实在是不能认同:

        1)MySQL(Innodb)是CA型数据库

        CAP理论是针对分布式系统的,MySQL(Innodb)在没有mycat这类中间件的情况下,完全就是单机的,怎么能这样划分呢?

        2)CAP最多三者取其二

        任何一个关于HBase的教材几乎都会表示HBase是为了一致性而放弃可用性的系统,是一种CP型。但是如果按照三者最多取其二的说法,那HBase就不该有可用性,平时读写又怎么可能会成功呢?

         其实按照CAP论文里的内容,个人感觉论文想表达的意思是在保证分区可容忍的分布式系统中,当遇到分区问题时,这个系统只能在可用性与一致性之间选择一个。说白了就是通常都是通过多副本来保证有几台机器宕机也不会影响服务,但是副本之间同步策略在宕机的情况下可能会导致数据不一样。

        可如果真的是这么想,其实就没有把单副本的分布式系统考虑进来了,难道只有一个副本的hdfs就不是分布式吗?没有slave的redis cluster 就不是分布式吗?

4.2 关于“CAP”的一家之言

        笔者通常都是这么理解CAP的,即这三种属性对于存储系统而言(包括非分布式)都不是一种要么有要么没有的属性,应该说0~100分,不同的系统在这三项上面的得分有高有低。

        对于单副本的一致性可以说是绝对的100分,但是完全不能容忍分区、即P可以说是0分;两个副本的hdfs跟200个副本的hdfs相比,两者的分区容错性也是不可同日而语的。像HBase这种系统,默认情况下发生宕机而且region没有重新分配的话,就是不会对相应区间内的数据提供读写服务,所以可用性不是太高;cassandra在合适的一致性设置下,就算发生某台机器宕机,仍然还可以从别的地方读取,只是可能读的不一样了。至于一致性、已经有很多说法了、什么强一致、弱一致、最终一致。

        所以本人通常都是按如下的雷达图来理解一个存储系统的“CAP”属性:


"CAP"雷达图

         灰色的三角表示这个存储系统各项都是满分。像HBase默认情况下,可以用红色三角表示,通过单台机器对外提供某一片数据的服务,保证了强一致性(可能一致性还不至于那么满,毕竟跟单副本的一致性比起来可能还是要差些);当HBase开启second replica的时候就像蓝色三角形那样,一致性变差了,但是可用性增强了,至于为什么没有画成完全的可用性,也是考虑到挂了两台机器、即使有second replica也是无法服务提供读写服务的;最后就是单机版的innodb,它就像图中的黑色三角形一致性可以说是绝对的,但是分区容错能力完全是0。

         izanami为了编写方便,本人也是参考黑色的三角形实现的。


5. 总结

        如果说mr/spark为olap系统带来了并行计算、orc/parquet为olap带来了列式存储、kylin为olap带来了预计算,那么izanami则为olap带来了预排序(或者叫预分组)。这也是笔者对分布式系统(无论是存储还是计算)给出的一份自己的答卷。

        下面是izanami的git地址,代码写的不好,很多地方都很粗糙,甚至可能不用特殊手段调不通,但是可以参考一下:

        https://github.com/kencao1994/izanami

你可能感兴趣的:(自制分布式数据库izanami)