简述LSM-Tree LSM-Tree 即 Log Structrued Merge Tree,这是一种分层有序,硬盘友好的数据结构。核心思想是利用磁盘顺序写性能远高于随机写。
LSM-Tree 并不是一种严格的树结构,而是一种内存+磁盘的多层存储结构。HBase、LevelDB、RocksDB这些 NoSQL 存储都使用了 LSM-Tree。

MemTable 是 LSM-Tree 在内存中的数据结构,只用于保存最新的数据,按照 Key 有序地组织这些数据。
LSM-Tree 没有规定用怎样的数据结构实现 MemTable,例如 HBase 使用跳表来保证内存中 Key 的有序性。
存在内存中的数据会因为断电丢失,所以我们通常使用 WAL,即预写日志的方式来保证数据的可靠。
WAL:预写日志,即事务的所有修改在提交之前要先写入 log 文件中
MemTable 达到一定大小后,会转化为 Immutable MemTable。Immutable MemTable 是将 MemTable 转为磁盘上的 SSTable 的一种中间状态。
转化过程中写操作由新的 MemTable 处理,过程中不阻塞数据更新操作。
SSTable 是有序键值对集合,是 LSM 树在磁盘中的数据结构。它是一种持久化、有序且不可变的键值村存储结构
SSTable 内部包含一系列可配置大小的 Block 块。这些 Block 的 index 会被存储在 SSTable 的尾部,用于帮助快速查找特定的 Block。当一个 SSTable 被打开时,index 表会被加载到内存,然后根据 key 在内存 index 中进行一个二分查找,查到该 key 对应的磁盘的 offset 后,去磁盘把响应的块数据读取出来。

当然,如果内存足够大,可以直接利用 MMAP 的技术把 SSTable 映射到内存中,提供更快的查找。
MemTable 达到一定大小会被 flush 到硬盘中变成 SSTable。在不同的 SSTable 中可能存在相同的 Key 记录。但这样会带来一些问题:
LSM 树中,我们按照下面的步骤处理写数据请求。
Compact 是 LSM 树中的关键操作,只有 Compact 的策略合理,才能及时有效地清除冗余的数据。
先介绍以下几个概念:

size-tiered 策略保证每层中每个 SSTable 的大小相近,同时限制每一层 SSTable 的数量。
如上图,每层限制有 N 个 SSTable,每层数量达到 N 后,触发 Compact 操作来合并这些 SSTable,放入下一层成为更大的 SSTable
当层数越来越大,单个 SSTable 的大小也会越来越大。该策略会导致空间放大比较严重。对每一层的 SSTable 来说,每个 key 的记录也可能存在多份。只有该层执行 Compact 操作才会消除这些冗余记录。

leveled 策略限制每一层总文件的大小。
leveld 同样将每一层划分为大小相近的 SSTable。并保证在一层内全局有序。这意味着与一个 Key 在每一层至多只有一条记录,不存在冗余记录。

下面展示 leveled 的 Compact 策略
Ⅰ. L1 总大小超过 L1 本身大小限制。

Ⅱ. LSM 树从 L1 中选择至少一个文件,然后把他和 L2 有交集的部分进行合并。生成的文件放在 L2。
如下图,L1 第二个 SSTable 的 Key 的范围覆盖了 L2 中前三个 SSTable,那么就需要将 L1 中第二个 SSTable 与 L2 中前三个 SSTable 执行 Compact。

Ⅲ. 如果 L2 合并后的大小超过 L2 的限制大小。则重复之前的操作,选至少一个文件然后合并到下一层。

多个不相干的合并可以并发进行

leveled 策略相比 size-tiered 策略来说,每层内的 Key 是有序、不重复的。这样就很好地控制了冗余 Key 的量。
查询过程中我们发现,在原始情况下,我们需要遍历所有的 SSTable。我们考虑以下方式,尝试优化查询的效率。