浅谈LSM-tree的理解

浅谈LSM-tree的理解

LSM_tree 全称是Log Structured Merge Trees(日志结构合并树)

被广泛应用于key-value键值对数据库,是一种存储结构/引擎,或者说是一种思想

像HBase就是典型基于LSM树思想设计的

核心思想

lsm_tree是一种分层,有序,面向磁盘的存储结构,核心思想就是充分利用了磁盘的批量顺序写入比随机写入性能高很多,最大的特点就是写入速度快,但相应的牺牲掉了一些读取的速度,我们都知道日志是系统打印出来的,就跟写日记一样,是一页一页往后面追加的,各种数据库的写前日志也是追加的,我们也可以把Log Structured 简单理解为追加,

但注意他还是一个merge tree,也就是说他还有合并的作用

原理及引用

我们知道 LSM_tree广泛应用于key-value键值对数据库,key-value 类型的存储系统最主要的就两个个功能,put(k,v):写入一个(k,v),get(k):给定一个 k 查找 v。

围绕着lsm树的核心思想进行优化,可以让写的性能达到最优,所以像普通的log写入方式全部都是append方式追加,不存在修改和删除,当然这样的弊端就是虽然写的能力大大加强了,但是却牺牲了部分读取的性能为代价,所以这种数据结构适用于多写少读的场景,那么想要支持复杂和高效的读取,就得做进一步的设计,lsm_tree就利用了内存加磁盘多层的合并结构的原因,基于这种结构在加上不同的优化实现,才造就了现在如HBase等的nosql数据库

在lsm_tree里最核心的数据结构就是sstable,全称是sorted string table,是一种拥有持久化,有序且不可变的键值存储结构,他的key和value都是任意的字节数组,并且提供了按照指定key和指定范围的key迭代遍历的功能,sstable内部包含了一系列可配置大小的block块,典型的大小是64kb,关于这些块的索引存在sstable的尾部,用于查找特定的block块,当一个sstable被打开的时候,index会被加载到内存中,然后根据key在index里进行一个二分查找,找到key以后去磁盘把对应的块数据读取出来,当然如果内存够大的话可以直接用mmap的方式映射到内存中

浅谈LSM-tree的理解_第1张图片

浅谈LSM-tree的理解_第2张图片

图片截取自网络,侵权删

LSM_树的写入过程

**(1)**当收到一个写的请求时,会先把这个操作写入到wal写前日志中,用作故障恢复

**(2)**写完wal之后会把这条数据写入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_树的读取过程

当收到一个读请求的时候,会直接先在内存里面查询,如果查询到就返回。

如果没有查询到就会依次下沉,知道把所有的Level层查询一遍得到最终结果。

读取的优化:

思考查询步骤,我们会发现如果SSTable的分层越多,那么最坏的情况下要把所有的分层扫描一遍,对于这种情况肯定是需要优化的,有那么几种方式可以进行优化

(1)压缩

(2)缓存

(3)布隆过滤器

(4)合并

这个在前面的写入流程中已经介绍过,通过定期合并瘦身, 可以有效的清除无效数据,缩短读取路径,提高磁盘利用空间。但Compaction操作是非常消耗CPU和磁盘IO的,尤其是在业务高峰期,如果发生了Major Compaction,则会降低整个系统的吞吐量,这也是一些NoSQL数据库,比如Hbase里面常常会禁用Major Compaction,并在凌晨业务低峰期进行合并的原因。

最后有的同学可能会问道,为什么LSM不直接顺序写入磁盘,而是需要在内存中缓冲一下? 这个问题其实很容易解答,单条写的性能肯定没有批量写来的块,这个原理其实在Kafka里面也是一样的,虽然kafka给我们的感觉是写入后就落地,但其实并不是,本身是 可以根据条数或者时间比如200ms刷入磁盘一次,这样能大大提升写入效率。此外在LSM中,在磁盘缓冲的另一个好处是,针对新增的数据在查询的时候可以快速的返回

HBase

自此我们再看如HBase等nosql数据库的设计,可以看出很多共同点

浅谈LSM-tree的理解_第3张图片

HBase的写入流程

Hbase中只有增添数据,所有的更新和删除操作都是在后续的合并中进行的,使得用户的写操作只要进入内存就可以立刻返回,实现了hbase的高速存储。Hbase使用memstore和storefile存储对表的更新

(1)首先数据在写入的时候会先写入写前日志hlog

(2)然后先将数据写入metastore,当metastore中的数据达到一定的阈值的时候,此时就会将数据flush到磁盘上成为一个Storefile

(3) 随着Storefile的数量不断增多,再次达到一个阈值之后,就会触发compact操作,将多个的storefile合并成一个,并进行版本的合并和数据的删除

(4)当storefile的文件通过compact合并成越来越大的文件以后,就会触发split操作,把当前Region Split成2个新的Region,父Region会下线,新Split出的2个子Region会被HMaster分配到相应的RegionServer上,使得原先1个Region的压力得以分流到2个Region上

flush,compact,split 这也就是我们所说的HBase的三大机制

总结

纵观HBase的写流程我们可以看出两者的核心思想都是将数据分层存储,通过不断合并不断增大的数据来将数据一层一层的落地,这也就是merage,也可叫做compact

大体思想可以简单理解为:

(1)使用写前日志来保障数据的高可用性

(2)对于不断append的数据首先写入在内存中,以便在读取的时候可以快速的返回,这也算是对读性能的一个优化吧

(3)然后merage/compact越来越大的文件,落地到下一层,采用有序的结构,以便于进行高效的合并,减少数据冗余,保证读取速度相对快速

这种思想适合于写多,读少的场景,或者读取最新数据多的场景,因为他们被缓存在内存中,可以快速的返回,不需要磁盘io操作

声明:文章仅是自己平时学习的过程的记录,如果有不对的或者侵权的地方请联系我改正,避免误导更多的人

你可能感兴趣的:(浅谈LSM-tree的理解)