LSM-Tree

文章参考:地址

简介

十多年前,谷歌发布了大名鼎鼎的"三驾马车"的论文,分别是GFS(2003年),MapReduce(2004 年),BigTable(2006年),为开源界在大数据领域带来了无数的灵感,其中在 “BigTable” 的论文中很多很酷的方面之一就是它所使用的文件组织方式,这个方法更一般的名字叫 Log Structured-Merge Tree。在面对亿级别之上的海量数据的存储和检索的场景下,我们选择的数据库通常都是各种强力的NoSQL,比如Hbase,Cassandra,Leveldb,RocksDB等等,这其中前两者是Apache下面的顶级开源项目数据库,后两者分别是Google和Facebook开源的数据库存储引擎。而这些强大的NoSQL数据库都有一个共性,就是其底层使用的数据结构,都是仿照“BigTable”中的文件组织方式来实现的,也就是我们今天要介绍的LSM-Tree。
LSM-Tree 是一种设计思想。在此思想下,可以带来极高的写入速度。但是稍微牺牲了读取的速度。另外要知道,在此设计下,无法对事务有很好的支持,还要知道,这种方式的写入方式,它是近实时的,在实时性上略有牺牲。 在此设计下,背后要进行merge,要花费很多的资源。
为什么LSM-Tree对事务没有很好的支持?为什么要进行merge?到底是一种什么样的结构?等等?下面开始正文介绍

概念

LSM-Tree全称是Log Structured Merge Tree,是一种分层,有序,面向磁盘的数据结构,其核心思想是充分利用磁盘的顺序写性能要远高于随机写性能这一特性,将批量的随机写转换为一次性的顺序写
LSM-Tree具体长这样:
在这里插入图片描述

SSTable

在LSM-Tree里面,核心的数据结构就是SSTable,全称是Sorted String Table,SSTable的概念其实也是来自于 Google 的 Bigtable 论文,那么具体长什么样子那,看下图:
在这里插入图片描述
如上图所示:SSTable是一种拥有持久化,有序且不可变的的键值存储结构,它的key和value都是任意的字节数组,并且了提供了按指定key查找和指定范围的key区间迭代遍历的功能。SSTable内部包含了一系列可配置大小的Block块,典型的大小是64KB,关于这些Block块的index存储在SSTable的尾部,用于帮助快速查找特定的Block。当一个SSTable被打开的时候,index会被加载到内存,然后根据key在内存index里面进行一个二分查找,查到该key对应的磁盘的offset之后,然后去磁盘把响应的块数据读取出来。当然如果内存足够大的话,可以直接把SSTable直接通过MMap的技术映射到内存中,从而提供更快的查找。
SSTable 在后台是要经过不断地排序合并,文件随着层次的加深,其大小也逐步变大。同时它也是可以启用压缩功能的,并且这种压缩不是将整个 SSTable 一起压缩,而是根据 locality 将数据分组,每个组分别压缩,这样的好处当读取数据的时候,我们不需要解压缩整个文件而是解压缩部分 Group 就可以读取。如图下图 leve0 的 SSTable 达到数据量的阀值之后,会经过排序合并形成 Level1 的 SSTable , Level1 的 SSTable 达到阀值之后,会经过排序合并成为 Level2 的 SSTable 文件
在这里插入图片描述

合并思想

LSM 树由多个SSTable组成,但是分为两部分,一个在内存区,一个在硬盘区,内存区一般叫做Memtable(增量数据)。 Memtable的结构具体可以是任何方便健值查找的数据结构,比如红黑树、 map 之类,甚至可以是跳表。硬盘区的数据结构为SSTable(基线数据),
数据被分为基线数据( SSTable )和增量数据( MemTable )两部分,基线数据被保存在磁盘中,当需要读取的时候会被加载到数据库的缓存中,当数据被不断插入(或者修改时)在内存中缓存增量数据,当增量数据达到一定阀值时,就把增量数据刷新到磁盘上,当磁盘上的增量数据达到一定阀值时再把磁盘上的增量数据和基线数据进行合并。这个本身是 LSM 的核心设计思想,非常朴素,就是将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操作批量写入磁盘,从而大幅度提升性能。

LSM-Tree写数据的过程

1,当收到一个写请求时,会先把该条数据记录在WAL Log里面,用作故障恢复。
2,当写完WAL Log后,会把该条数据写入内存的SSTable里面,也称Memtable。注意为了维持有序性在内存里面可以采用红黑树或者跳跃表相关的数据结构。
3,当Memtable超过一定的大小后,会在内存里面冻结,变成不可变的Memtable,同时为了不阻塞写操作需要新生成一个Memtable继续提供服务。
4,把内存里面不可变的Memtable给dump到到硬盘上的SSTable层中,此步骤也称为Minor Compaction,这里需要注意在L0层的SSTable是没有进行合并的,所以这里的key range在多个SSTable中可能会出现重叠,在层数大于0层之后的SSTable,不存在重叠key。
5,当每层的磁盘上的SSTable的体积超过一定的大小或者个数,也会周期的进行合并。此步骤也称为Major Compaction,这个阶段会真正 的清除掉被标记删除掉的数据以及多版本数据的合并,避免浪费空间,注意由于SSTable都是有序的,我们可以直接采用merge sort进行高效合并。

LSM-Tree读数据的过程

1,当收到一个读请求的时候,会直接先在内存里面查询,如果查询到就返回。
2,如果没有查询到就会依次下沉,直到把所有的Level层查询一遍得到最终结果。
思考查询步骤,我们会发现如果SSTable的分层越多,那么最坏的情况下要把所有的分层扫描一遍,对于这种情况肯定是需要优化的,如何优化?在 Bigtable 论文中提出了几种方式:

  1. 压缩
    SSTable 是可以启用压缩功能的,并且这种压缩不是将整个 SSTable 一起压缩,而是根据 locality 将数据分组,每个组分别压缩,这样的好处当读取数据的时候,我们不需要解压缩整个文件而是解压缩部分 Group 就可以读取。
  2. 缓存
    因为SSTable在写入磁盘后,除了Compaction之外,是不会变化的,所以我可以将Scan的Block进行缓存,从而提高检索的效率
  3. 索引,Bloom filters
    正常情况下,一个读操作是需要读取所有的 SSTable 将结果合并后返回的,但是对于某些 key 而言,有些 SSTable 是根本不包含对应数据的,因此,我们可以对每一个 SSTable 添加 Bloom Filter,因为布隆过滤器在判断一个SSTable不存在某个key的时候,那么就一定不会存在,利用这个特性可以减少不必要的磁盘扫描。
  4. 合并
    这个在前面的写入流程中已经介绍过,通过定期合并瘦身, 可以有效的清除无效数据,缩短读取路径,提高磁盘利用空间。但Compaction操作是非常消耗CPU和磁盘IO的,尤其是在业务高峰期,如果发生了Major Compaction,则会降低整个系统的吞吐量,这也是一些NoSQL数据库,比如Hbase里面常常会禁用Major Compaction,并在凌晨业务低峰期进行合并的原因。
    最后有的同学可能会问道,为什么LSM不直接顺序写入磁盘,而是需要在内存中缓冲一下? 这个问题其实很容易解答,单条写的性能肯定没有批量写来的块,这个原理其实在Kafka里面也是一样的,虽然kafka给我们的感觉是写入后就落地,但其实并不是,本身是 可以根据条数或者时间比如200ms刷入磁盘一次,这样能大大提升写入效率。此外在LSM中,在磁盘缓冲的另一个好处是,针对新增的数据,可以直接查询返回,能够避免一定的IO操作。

LSM-Tree 数据修改过程

LSM-Tree 存储引擎的更新过程其实并不存在,它不会像 B 树存储引擎那样,先经过检索过程,然后再进行修改。它的更新操作是通过追加数据来间接实现,也就是说更新最终转换为追加一个新的数据。只是在读取的时候,会从 Level0 层的 SSTable 文件开始查找数据,数据在低层的 SSTable 文件中必然比高层的文件中要新,所以总能读取到最新的那条数据。也就是说此时在整个 LSM Tree 中可能会同时存在多个 key 值相同的数据,只有在之后合并 SSTable 文件的时候,才会将旧的值删除。

LSM-Tree 数据删除过程

LSM-Tree 存储引擎的对数据的删除过程与追加数据的过程基本一样,区别在于追加数据的时候,是有具体的数据值的,而删除的时候,追加的数据值是删除标记。同样在读取的时候,会从 Level0 层的 SSTable 文件开始查找数据,数据在低层的 SSTable 文件中必然比高层的文件中要新,所以如果有删除操作,那么一定会最先读到带删除标记的那条数据。后期合并 SSTable 文件的时候,才会把数据删除。

B+Tree VS LSM-Tree

传统关系型数据采用的底层数据结构是B+树,那么同样是面向磁盘存储的数据结构LSM-Tree相比B+树有什么异同之处呢?
LSM-Tree的设计思路是,将数据拆分为几百M大小的Segments,并是顺序写入。
B+Tree则是将数据拆分为固定大小的Block或Page, 一般是4KB大小,和磁盘一个扇区的大小对应,Page是读写的最小单位。
在数据的更新和删除方面,B+Tree可以做到原地更新和删除,这种方式对数据库事务支持更加友好,因为一个key只会出现一个Page页里面,但由于LSM-Tree只能追加写,并且在L0层key的rang会重叠,所以对事务支持较弱,只能在Segment Compaction的时候进行真正地更新和删除。
因此LSM-Tree的优点是支持高吞吐的写(可认为是O(1)),这个特点在分布式系统上更为看重,当然针对读取普通的LSM-Tree结构,读取是O(N)的复杂度,在使用索引或者缓存优化后的也可以达到O(logN)的复杂度。
而B+tree的优点是支持高效的读(稳定的OlogN),但是在大规模的写请求下(复杂度O(LogN)),效率会变得比较低,因为随着insert的操作,为了维护B+树结构,节点会不断的分裂和合并。操作磁盘的随机读写概率会变大,故导致性能降低。
还有一点需要提到的是基于LSM-Tree分层存储能够做到写的高吞吐,带来的副作用是整个系统必须频繁的进行compaction,写入量越大,Compaction的过程越频繁。而compaction是一个compare & merge的过程,非常消耗CPU和存储IO,在高吞吐的写入情形下,大量的compaction操作占用大量系统资源,必然带来整个系统性能断崖式下跌,对应用系统产生巨大影响,当然我们可以禁用自动Major Compaction,在每天系统低峰期定期触发合并,来避免这个问题。
阿里为了优化这个问题,在X-DB引入了异构硬件设备FPGA来代替CPU完成compaction操作,使系统整体性能维持在高水位并避免抖动,是存储引擎得以服务业务苛刻要求的关键。

总结

elasticsearch搜索引擎中有用到LSM-Tree, clickHouse中有用到LSM-Tree,LSM-Tree其牺牲了部分读取的性能,通过批量顺序写来换取了高吞吐的写性能,这种特性在大数据领域得到充分了体现,最直接的例子就各种NoSQL在大数据领域的应用,学习和了解LSM-Tree的结构将有助于我们更加深入的去理解相关NoSQL数据库的实现原理,掌握隐藏在这些框架下面的核心知识

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值