数据的存储结构浅析LSM-Tree和B-tree


本篇主要讨论的是不一样存储结构(主要是LSM-tree和B-tree),它们应对的不一样场景,所采用的底层存储结构,以及对应用以提高效率的索引。

所谓数据库,最基础的功能,就是保存数据,而且在须要的时候能够方便地检索到须要的数据。在这个基础上,演化出了不一样的数据库系统,以及多种索引机制帮助检索数据。这篇咱们就来讨论几种常见的数据存储和索引机制,主要是B-tree,LSM-Tree,以及它们对应的优缺点。java

顺序存储与哈希索引

试想一下,若是按照保存数据,而且在须要的时候能够方便地检索到须要的数据这一标准,设计一个简单的数据库,那么最简单的作法应该怎么作呢?mysql

最简单的作法,就是经过顺序保存数据到一个日志文件中。而后经过索引,这里以哈希索引为例(好比java的hashMap),记录每条数据的key以及对应的位移,将其保存到内存中,避免随机检索巨大的开销。值得注意的是,引入索引,虽然会显著提升查询效率,但会略微下降写入速度。由于每次写入的时候都须要额外写入到哈希索引中,这一点对大部分索引都是适用的。算法

哈希索引

上图为哈希索引示例,下面是顺序存储在磁盘上是日志数据,上面的内存中的哈希索引。哈希索引是不少复杂索引的基础,好比在mysql中就有提供哈希索引的选项,固然哈希索引并不经常使用,由于它最基础,同时也意味着它最容易被优化。sql

上述形式的顺序存储+哈希索引中,增长数据和查找数据相对容易理解,而修改数据则能够经过将新数据追加到文件尾部,从新生成索引实现,删除操做则能够给与哈希索引一个标识符实现(如对应key置为-1)。数据库

但这样有一个问题,可能会出现磁盘耗尽的状况。针对这一个问题,咱们能够将日志文件拆分红多个必定大小的文件段(这里的文件段能够理解为接受统一管理的数据文件)。当一个文件段达到必定大小,好比4kb的时候,就关闭它,新建一个文件段。而旧的文件段能够进行压缩,前面提到过,删除和修改都是经过追加日志相同的key-value实现的,那么早先的数据其实就已经没用的,因此压缩的时候只保留最新的key数据。压缩到过程以下面这张图所示:数据结构

压缩数据

图中上面的部分就是顺序存储的数据,能够发现其中有不少的key都是相同的,这是由于顺序存储状况下,修改数据就是不断新写入相同的key。这种状况咱们要的只有相同key的最新的value。因此压缩过程也是一个清理磁盘的过程。分布式

压缩合并过程能够由后台进行默默进行,因此没必要担忧这个过程影响查询性能。上图中只有一个数据文件段,但实际上能够有多个文件段,多个文件段也能够合并(相似于Hbase中多个文件的merge操做)。oop

固然这样的优化能够极大程度节省空间,但必不可少得会给检索带来时间上的损耗。在多个文件段的状况,每一个文件段都有本身的哈希索引,故而要查找数据会首先根据key查找内存中最新文件段的哈希索引,若是找不到,那么找次新文件段的哈希索引,接着找次新的哈希索引,直到遍历全部文件段的哈希索引。性能

综上,顺序存储+哈希索引优势明显,简单,高效。缺点是哈希索引需所有存到内存(若是将哈希索引放到磁盘那至关于放弃了检索的高效),而且难以实现区域查询优化

为了解决它的这些问题,咱们能够将哈希索引作一些小小的改变。具体来讲,就是让文件段的数据,按key进行排序存储。这样会带来哪些改变呢?

SSTable和LSM tree

将数据文件段中的数据按key进行排序,而且保证相同的key只出现一次(在压缩的时候保证),这种格式就称之为排序字符串表,简称SStable(Sorted String Table)。

将数据按Key进行排序后有如下几个好处:

  1. 合并更加简单高效,即便数据文件段大于内存,也可使用相似归并排序算法进行数据段的压缩,即将一个大文件拆成多个小数据进行压缩。若是多个文件段中有相同的key,那么以最新的文件段的key为准。
  2. 缓解哈希索引须要整个hashMap存储到内存的窘境。由于key是排序的,因此能够在内存中维持一个稀疏索引,存储每一个key的范围,具体见下图。而且这个稀疏索引所需的内存空间是很小的。

稀疏索引与SStable

经过稍微改变一下文件段的结果,就得到如此多的好处。但还有一个问题,前面的哈希索引是基于顺序存储的日志文件的,要让SStable按key排序,那就不能顺序存储磁盘了呀(即没法存储的时候当即写入磁盘)!!的确是这样,虽然也可使用相似B-tree来实现磁盘上的排序存储,但转换下思路,其实将数据先保存在内存中其实更加方便

具体实现流程,是在内存中维护一个相似TreeMap的数据结构用于存储数据(TreeMap底层是基于红黑树对存储的key进行排序的。不管咱们按照什么样的顺序存储数据,TreeMap老是会将数据按照key进行排序)。这个TreeMap称为内存表,当内存表超过必定阈值的时候,就将其写入到磁盘中,成为SStable,由于已经排好序,因此写入的效率其实比想象的要高。后期再对磁盘中的SStable进行压缩与合并操做。

当须要根据key检索的时候,会先去内存表中检索,找不到再去最新的SStable,再去次新的SStable,直到遍历彻底部。

上述这种索引结构被称为之LSM-Tree,全称是Log-Structured Merge-Tree,即日志合并树。而这种基于合并和压缩文件原理的存储引擎被称为LSM存储引擎,其中比较为人所知的是Hbase。

B-Tree

最后,咱们再来讨论流传最久的数据库村粗结构。与LSM-Tree这几年才逐渐为人所知不一样,B-tree存储结构担得起经久不衰这四个字。

B-tree自己是一种树形的数据结构,更具体点说是一颗平衡查找树,它也是经过存储顺序的key存储数据(这一点和SStable有类似之处)。不一样于前面的LSM-tree的文件段,B-tree将数据库分解成固定大小的块或页,一般一个页大小是4kb。这种分配方法更加贴合底层的磁盘。

当须要进行查找的时候,老是从根开始,根据范围跳转到对应的key,而其对应的value能够是值自己,也能够是指向存储对应数据的磁盘地址。下图是一个具体的例子:

B-tree

而在更新或插入的时候,有可能会出现没有足够空间来容纳新key的问题,这时候就会发生分裂。分裂操做是比较危险的,在分裂的时候若是数据库崩溃,可能会致使索引被破坏。为了防止这个问题,能够引入预写日志(write-ahead log,WAL)机制。mysql的binlog就是这样的东西。具体说就是在执行操做的时候,将这次操做写入一个只容许追加的文件中,这样一来当崩溃的时候就能够检查日志并进行恢复。

存储结构的比对

从使用的角度上来讲,B-tree等索引存储结构多用于OLTP型的数据库,由于这类数据库主要以事务,或是行级别的读取和存储为主的(好比Mysql)。换句话说,这种类型的数据库更多的操做是小批量或单行级别的更新或读取,而且可能还有事务方面的需求,这种类型正是B-tree结构所擅长的。

而 LSM-tree则多用于大规模数据状况下的检索分析和快速写入的状况。在写入的性能上,由于上直接写入内存再按期刷入到磁盘中,因此写入操做对用户的感知而言上很是迅速的。而检索速度也由于key顺序存储,能够快速定位到key对应的位置,于是具备较好的检索性能。

可是LSM-tree比较显著的应用方向仍是在大规模分析这方面,在大规模分析(OLAP)场景下,数据一般都是列式存储,而且须要全表扫描。其中磁盘数据可使用二进制进行压缩,读取的时候能够有效减小磁盘IO的处理时间(与之相比,B-tree等存储结构就没法充分压缩,由于每次都只处理小部分数据)。同时在存储文件中还能再进一步切分,好比将列式数据按照水平切分红不一样的Page,同时存储一些简单的索引,用来指定不一样Page大概范围,Hadoop的存储数据格式Parquet就是相似的设计。

小结

本篇主要讨论了几种基础的存储结构和索引以及其对应使用场景,限于篇幅,更多索引的变种没法多加讨论,好比B-tree的优化版B+tree,多列索引等。

其实大部分数据库或者说存储引擎,都是针对不一样的场景下,在旧有的基础上进行必定程度的微改造创新,但大致的结构依旧是以上述两三种为准,了解了上述几种结构,对数据存储方面应该可以有一个感性的认知了。

此外本章多参考自《DDIA》第三章节,对分布式系统感兴趣的童鞋能够看看此书,确定不会失望的。

以上~

相关文章
相关标签/搜索