LSM-Tree 介绍

543 阅读6分钟

  LSM-Tree(Log Structured Merge Tree)是很多高度可扩展的分布式 KV 类型数据库的底层数据结构。LSM-Tree 不是一种单一的数据结构,而是结合了多种数据结构,并充分利用了各自的优势。LSM-Tree 只允许进行追加方式写入,这保证了比较高的写入速度,但也导致在 LSM-Tree 无法进行即时更新/删除。另外,LSM-Tree 通过维护一些额外的索引信息,保证了读取的速度。

⒈ LSM-Tree 的结构

LSM-Tree 结构示意图.png

⓵ MemTable

  MemTable 在内存中缓存客户端写请求发送的数据,通常用平衡二叉树跳跃表实现。MemTable 中存储的所有数据都是按照 key 有序排列的,当 MemTable 中存储的数据量到达设置的阈值时,这些数据会被写入磁盘,生成一个新的 SSTable。与此同时,一个新的空的 MemTable 被创建出来继续响应客户端的写请求。

客户端的读请求最先也是同 MemTable 进行交互

⓶ SSTTable

  当 MemTable 中的数据量到达设置的阈值或 LSM-Tree 在后台对已有 SSTable 进行压缩操作时,新的 SSTable 就会被创建。SSTable 中的数据也是按照 key 有序存储,以提高读操作的性能。

同一个 SSTable 中的 key 是有序的,但不同的 SSTable 之间的 key 不保证有序

  随着时间的推移,写操作会导致磁盘上 SSTable 数量增加,这些 SSTable 会被不断的重新组织并压缩成从 level 0level n 的不同级别。level 0 级的 SSTable 只是简单的将 MemTable 中的数据进行转储,并不进行其他的处理,所以多个 SSTable 中会出现重复的 keylevel 1 及以后的级别的 SSTable 中则不会出现这种情况。

 高 level 中的 SSTable 是通过压缩合并低 level 中的 SSTable 得到的
 已有的 SSTable 在经过压缩合并后会生成新的、数据量更大的 SSTable 存入更高的 level 中,这样既可以减少磁盘中文件的数量,还有助于提升读操作的性能
 往往越高的 level 中的 SSTable 的 size 会越大,SSTable 之间出现重复 key 的情况会越少

  在实际应用中,为了提升 SSTable 读操作的性能,SSTable 中还会存储一些额外的元数据:

  • 稀疏索引(sparse indexes)

  稀疏索引提供 SSTable 中的 keykey 的范围到 key 的具体存储地址或偏移量的映射。SSTable 中的数据按照 key 有序存储,在 SSTable 中查找某个特定的 key 时,可以先通过稀疏索引定位查找的起始位置,然后再在 SSTable 中进行二次查找。

  • 块索引(block-based indexes)

  块索引的原理与稀疏索引类似,只不过块索引将 keykey 的范围映射到了指定的存储块(block)。SSTable 中的数据会被分成多个指定大小的块,每个块都会被分配一个指定的 ID 或偏移量,读操作先通过块索引定位要查询的数据所在的块,然后再在特定的块中进行二次查找。相对于稀疏索引,块索引的粒度相对比较粗。

  • bloom filter

  bloom filter 可以快速确定要查找的 key 是否存在于指定的 SSTable 中。由于存在哈希碰撞,bloom filter 可能会误报,但不会漏报。

以上这些元数据,不一定同时存在于 SSTable 中,具体情况取决于各个应用中的代码实现。

⒉ LSM-Tree 操作

⓵ 新增/更新

  LSM-Tree 中不存在即时更新,所有的更新操作与新增操作过程相同,都是将数据追加到 MemTable 中。在后续的压缩过程中,这些更新的新值会被保留,而旧值会被移除。

  当 MemTable 中的数据量达到设置的阈值时,当前的 MemTable 会变成 immutable MemTable,同时还会生成一个新的空的 MemTable 来响应客户端的请求。在将 immutable MemTable 中的数据写入磁盘之前,这些数据首先会被写入 WAL(write ahead log) 日志,以防系统出现故障,从而保证数据的持久性。之后,这些数据会被写入磁盘生成一个新的 SSTable。在这些写入操作都完成后,immutable MemTable 会被释放。

⓶ 删除

  与更新操作类似,LSM-Tree 也没法进行即时删除。删除操作同样是在 MemTable 中追加一条记录,该记录的值是一个特殊的标记,表示对应的 key 被删除。在后续的压缩过程中,这些有特殊标记的值的 key 都会被移除。

⓷ 读取

  读操作首先在 MemTable 中查找,如果在 MemTable 中找不到则需要在 SSTable 中查找。在 LSM-Tree 中,level 越高,level 中的 SSTable 中的数据越旧,而 LSM-Tree 的写入操作是以追加的方式进行,所以,读操作会从低 level 到高 level 进行。SSTable 中的 bloom filter 可以快速确定要查找的 key 是否存在于当前的 SSTable,而稀疏索引或块索引又可以大幅缩小查找范围。

  如果一个 key 不存在,则读操作需要在访问完所有的 SSTable 以后才能确定。SSTable 的数量越多,这个过程所需要的时间就越长。为了提高读操作的效率,需要定期对 SSTable 进行压缩。

⓸ 压缩

  压缩操作既可以减少磁盘中 SSTable 的数量,还可以回收那些由于更新/删除操作导致的已经过期的数据所占用的磁盘空间。低 level 中的 SSTable 经过压缩整合后生成一个更大的 SSTable 存入更高一级的 level,这使得磁盘中整体 SSTable 的数量始终处于可控的状态。

  压缩操作会消耗大量的 I/O,如果使用不当会导致整个系统处于饥饿状态,同时还会影响读/写操作的性能。

⒊ LSM-Tree 的不足

  压缩是整个 LSM-Tree 中最消耗资源的过程,整个过程涉及到数据的压缩/解压缩、数据的复制以及 key 的比较。另外,压缩还涉及大量的 I/O 以及需要用到额外的存储空间。所以,LSM-Tree 的主要不足之处就是压缩,这会影响读写性能。

  除此之外,在比较极端的情况下,读操作可能需要查找很多个 SSTable,甚至查找完所有的 SSTable 之后发现 key 不存在,这会使得读操作的速度变慢。