LSM Tree原理
在正式介绍LSM Tree之前,我想先提出一个问题的答案:为什么需要LSM Tree ?
LSM Tree的主要应用领域是磁盘KV存储。在传统的数据结构,如B+树中,其大多的特点都是具有非常良好的读性能,其都是应对多读少写的应用场景。但是由于磁盘寻道时间以及磁头移动等因素,其没有良好的写性能。所以这就引入了LSM Tree这种数据结构。
LSM Tree全程是Log Structed Merge Tree。中文名字叫做日志结构的合并树,从名字上我们可以大概可以推断出以下几点,其也是LSM Tree的主要工作原理。
- 其和我们的日志设计有着异曲同工之妙
- 其是树结构
- 其操作过程中伴有合并操作
我们来回顾一个日志的设计。就拿系统日志举例,其每次写日志都是追加这个日志信息到整个日志文件的尾部,这样我们写文件的性能就是O(1)
了。
LSM Tree就是采用的这样的设计思想,不过其存储的就是KV键值对。但是这样必不可免的就会遇到问题,假如后面我们把a这个key设置为2,那么我们就会把<a,2,2>
这样的键值对继续插入到我们的文件中。这样我们需要从后至前遍历,因为原来的数据<a,1,1>
是过期的,我们想要拿到的是最新的数据。
这样会带来两个问题:
- 无限的插入数据会导致数据的无限增长,旧数据需要删除
- 这样会导致我们的搜索效率是O(n)
LSM Tree的归并操作
首先我们先来简单介绍一下如何去抑制数据的无限增长。对于过期的数据起始我们很容易就想到GC
垃圾回收机制。但是这里我们要注意几个点:
- GC不能妨碍正常的进程逻辑,所以最好是另起一个进程来完成GC
- 对于同一个文件如果一边往里面插入数据,一边进行Merge操作,这样就会导致数据的 “线程安全” 问题。所以我们需要对文件进行分段,对于新输入的数据放到当前文件,这个是GC不许回收的。当这个“当前文件”的内容达到一个阈值,其就会被标记成旧文件。之后的新数据会再开一个文件存储。
那么整个GC是怎么回收空间的呢?
我们拿上图的三个文件以及LevelDB的应用举例。整个LevelDB分为L0~L6总共七层存储空间,每一层的空间都是上一层的10倍。当空间占用达到阈值的时候,就会触发归并操作。假设上面的三个文件都处于L0层,其中文件A的的Key范围是[a,e],文件B的范围是[a,i],文件C的范围是[b,z] 。可以很清楚的看到文件A、B、C互有重叠,这就会引发L0层的归并,使这三个文件归并到L1,L0的原有数据也就消失了。
那么根据这个结构,我们很容易就推断出一个规律:除了L0层之外,其他层之内的Key都是唯一的,其他层之间的Key不保证唯一性且越上层的Key越代表最新数据。
LevelDB中的应用
其实上面讲的也就是LevelDB中SLM Tree的使用了。但是除了硬盘之上,LevelDB还在内存上做了优化:对于一个数据源的数据来说,其首先会用流式在内存中写入,形成MemTable
,同时,为了能够恢复文件,其还会写入日志文件LOG
。之后,当这个MemTable
的大小达到阈值之后,这些数据会被以批量的形式形成IMemTable
,同时也会落地到磁盘中(这个时候会清理LOG中上一批的数据)。这个时候如果还有数据写入,就写入一个新的MemTable
中,如此循环。
LSM Tree的查询
根据上图我们可以看到,数据不仅在内存中出现,还在磁盘中出现。首先,对于一个Key来说,如果我们在内存的MemTable中找到,那就直接返回了,因为内存中的Key肯定是最新的。但是如果我们没找到,就需要去磁盘了,根据LSM Tree 的建立规则,我们很容易知道,越上层的数据越新。同时每一层每个文件都有这个文件的Key范围,那么我们就可以利用布隆过滤器在获得常量级的查询效率。
参考文献
无