B+树随着mysql Innodb引擎的普遍推广愈来愈被你们所熟知,而前不久我在研究Raft算法时,偶然发现了一种和B+树相似的数据结构——LSM树(Log-Structured-Merge-Tree 日志结构合并树),它是Google发表的论文 Big Table 中提到的一种颇有趣的文件组织数据结构, 现现在已经被运用在不少工业界的产品之中了:HBase、Cassandra、LevelDB、RocksDB等等。今天就来研究研究LSM树的原理。html
LSM树(Log-Structured-Merge-Tree)和B+树相似,它们被设计出来都是为了更好地将数据存储到大容量磁盘中。相对于B+树,LSM树拥有更好的随机写性能。在下面的一个ACM的报告中能够看到:mysql
磁盘顺序写的性能有些颠覆咱们的常识的,在上面的例子中,磁盘顺序写的吞吐量甚至可以超过内存随即写的吞吐量。而LSM树正是利用了这一点,它经过将磁盘随机写操做转化为顺序写操做,从而将随机写操做的吞吐量提升了好几个数量级。那么它是如何转换的呢?下面咱们先来看看它的基本思想。算法
LSM树会将全部的数据插入、修改、删除等操做保存在内存之中,当此类操做达到必定的数据量后,再批量地写入到磁盘当中。而在写入磁盘时,会和之前的数据作合并。在合并过程当中,并不会像B+树同样,在原数据的位置上修改,而是直接插入新的数据,从而避免了随机写。sql
LSM树的结构是横跨内存和磁盘的,包含memtable、immutable memtable、SSTable等多个部分。数据库
顾名思义,memtable是在内存中的数据结构,用以保存最近的一些更新操做,当写数据到memtable中时,会先经过WAL的方式备份到磁盘中,以防数据由于内存掉电而丢失。数据结构
预写式日志(Write-ahead logging,缩写 WAL)是关系数据库系统中用于提供原子性和持久性(ACID属性中的两个)的一系列技术。在使用WAL的系统中,全部的修改在提交以前都要先写入log文件中。函数
memtable可使用跳跃表或者搜索树等数据结构来组织数据以保持数据的有序性。当memtable达到必定的数据量后,memtable会转化成为immutable memtable,同时会建立一个新的memtable来处理新的数据。性能
顾名思义,immutable memtable在内存中是不可修改的数据结构,它是将memtable转变为SSTable的一种中间状态。目的是为了在转存过程当中不阻塞写操做。写操做能够由新的memtable处理,而不用由于锁住memtable而等待。大数据
SSTable(Sorted String Table)即为有序键值对集合,是LSM树组在磁盘中的数据的结构。若是SSTable比较大的时候,还能够根据键的值创建一个索引来加速SSTable的查询。下图是一个简单的SSTable结构示意:设计
memtable中的数据最终都会被转化为SSTable并保存在磁盘中,后续还会有相应的SSTable日志合并操做,也是LSM树结构的重点。
最终LSM树的结构能够由下图简单表示:
上面介绍了LSM的基本思想和结构,下面来看看它们是怎么配合起来完成一个数据系统最多见的CRUD流程。
写操做首先须要经过WAL将数据写入到磁盘Log中,防止数据丢失,而后数据会被写入到内存的memtable中,这样一次写操做即已经完成了,只须要1次磁盘IO,再加1次内存操做。相较于B+树的屡次磁盘随机IO,大大提升了效率。随后这些在memtable中的数据会被批量的合并到磁盘中的SSTable当中,将随机写变为了顺序写。
当有删除操做时,并不须要像B+树同样,在磁盘中的找到相应的数据后再删除,只须要在memtable中插入一条数据看成标志,如delKey:1933
,当读操做读到memtable中的这个标志时,就会知道这个key已被删除。随后在日志合并中,这条被删除的数据会在合并的过程当中一块儿被删除。
更新操做和000删除操做相似,都是只操做memtable,写入一个标志,随后真正的更新操做被延迟在合并时一并完成。
查询操做相较于B+树就会很慢了,读操做须要依次读取memtable、immutable memtable、SSTable0、SSTable1......。须要反序地遍历全部的集合,又由于写入顺序和合并顺序的缘故,序号小的集合中的数据必定会比序号大的集合中的数据新。因此在这个反序遍历的过程当中一旦匹配到了要读取的数据,那么必定是最新的数据,只要返回该数据便可。可是若是一个数据的确不在全部的数据集合中,则会白白得遍历一遍。
读操做看上去比较笨拙,所幸能够经过布隆过滤器来加速读操做。当布隆过滤器显示相应的SSTable中没有要读取的数据时,就跳过该SSTable。
布隆过滤器其实是一个很长的二进制向量和一系列随机映射函数。布隆过滤器能够用于检索一个元素是否在一个集合中。它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。
还有上面提到的索引文件,也能够加速读操做。
由前面的增删改查操做来看,合并操做是LSM树最重要的操做。
合并操做有两个主要的做用:
目前普遍使用的有两种合并策略,size-tiered策略和leveled策略
size-tiered策略是HBase采用的合并策略,具体内容是当某个规模的集合达到必定的数量时,将这些集合合并为一个大的集合。好比有5个50个数据的集合,那么就将他们合并为一个250个数据的集合。这种策略有一个缺点是当集合达到必定的数据量后,合并操做会变得十分的耗时。
leveled策略是LevelDB和RocksDB采用的合并策略,size-tiered策略由于会产生大数据量的集合,因此会形成突发的IO和CPU资源的消耗,因此leveled策略使用了分层的数据结构来代替原来的大数据集合。
leveled策略将集合的大小限制在一个小的范围内如5MB,并且将集合划分为不一样的层级。每个层级的集合总大小是固定且递增的。如第一层为50MB,第二层为500MB...。当某一层的数据集合大小达到上限时,就会从这一层中选出一个文件和下一层合并,或者直接提高到下一层。若是在合并过程当中发现了数据冲突,则丢弃下一层的数据,由于低层的数据老是更新的。
同时leveled策略会限制,除第一层外。其余的每一层的键值都不会重复。这是经过合并时剔除冗余数据实现的,以此来加速在同一层内数据的线性扫描速度。
LSM树牺牲了小部分读性能,而大幅度提升了写性能,因此很适合写多读少的场景,在这种场景下比B+树更加可以胜任。经过深刻了解,能够发现LSM树的合并策略会大大影响到LSM树的性能,因此应该根据具体的场景,灵活地选择相应的策略。
邀舞卡王老魔的代码备忘录