基本原理
LSM 树(Log-Structured Merge Tree)牺牲了一定的读性能来换取写入数据的高性能,Hbase、Cassandra、LevelDB 都是用这种算法作为存储的引擎。
它的思想很简单,数据首先会写入到一个叫做 MemTable 的内存结构中,在 MemTable 中数据是按照写入的 Key 来排序的。为了防止 MemTable 里面的数据因为机器掉电或者重启而丢失,一般会通过写 Write Ahead Log 的方式将数据备份在磁盘上。
MemTable 写满之后,就被转换成 Immutable MemTable,然后再创建一个空的 MemTable 继续写。这个 Immutable MemTable,也就是只读的 MemTable,它和 MemTable 的数据结构完全一样,唯一的区别就是不允许再写入了。
Immutable MemTable 也不能在内存中无限地占地方,会有一个后台线程,不停地把 Immutable MemTable 复制到磁盘文件中,然后释放内存空间。每个 Immutable MemTable 对应一个磁盘文件,MemTable 的数据结构跳表本身就是一个有序表,写入的文件也是一个按照 Key 排序的结构,这些文件就是 SSTable(Sorted String Table)。把 MemTable 写入 SSTable 这个写操作,因为它是把整块内存写入到整个文件中,这同样是一个顺序写操作。
当 SSTable 达到一定数量时,我们会将这些 SSTable 合并,减少文件的数量,因为 SSTable 都是有序的,所以合并的速度也很快。
当从 LSM 树里面读数据时,我们首先从 MemTable 中查找数据,如果数据没有找到,再从 SSTable 中查找数据。因为存储的数据都是有序的,所以查找的效率是很高的,只是因为数据被拆分成多个 SSTable,所以读取的效率会低于 B+ 树索引。

分层结构
LSM 树采用分层的结构来组织 SSTable。较低层次的 SSTable 包含较新的数据,而较高层次的 SSTable 包含较旧的数据。随着时间的推移,数据会逐渐从较低层次合并到较高层次。
这种分层结构有助于减少磁盘 I/O 操作。在进行数据读取时,系统会首先在 MemTable 中查找,如果未找到,则从较低层次的 SSTable 开始依次查找,直到找到数据或到达最高层次的 SSTable。
合并操作
合并操作是 LSM 树的一个重要组成部分。定期地,系统会将多个 SSTable 合并成一个更大的 SSTable。合并操作有两个主要目的:一是减少 SSTable 的数量,从而减少在读取数据时需要查找的文件数量,提高读取性能;二是通过合并可以对数据进行压缩和去重,节省磁盘空间。
在合并过程中,会将多个 SSTable 中的数据读取出来,进行排序和合并,然后将合并后的数据写入到一个新的 SSTable 中。同时,旧的 SSTable 会被删除。
查找的性能
查找的过程也是分层查找,先去内存中的 MemTable 和 Immutable MemTable 中找,然后再按照顺序依次在磁盘的每一层 SSTable 文件中去找,只要找到了就直接返回。这样的查找方式其实是很低效的,有可能需要多次查找内存和多个文件才能找到一个 Key,但实际的效果也没那么差,因为这样一个分层的结构,它会天然形成一个非常有利于查找的情况:越是被经常读写的热数据,它在这个分层结构中就越靠上,对这样的 Key 查找就越快。
比如说,最经常读写的 Key 很大概率会在内存中,这样不用读写磁盘就完成了查找。即使内存中查不到,真正能穿透很多层 SStable 一直查到最底层的请求还是很少的。另外,在工程上还会对查找做很多的优化,比如说,在内存中缓存 SSTable 文件的 Key,用布隆过滤器避免无谓的查找等来加速查找过程。这样综合优化下来,可以获得相对还不错的查找性能。
优点
- 高写入性能:由于数据首先写入内存中的 MemTable,避免了每次写入都直接操作磁盘,大大减少了磁盘 I/O 操作,因此具有很高的写入性能,特别适合处理大规模的写入请求。
- 空间利用率高:通过合并操作对数据进行压缩和去重,有效地节省了磁盘空间。
- 支持顺序读取:SSTable 中的数据是按照键值对有序存储的,因此对于范围查询和顺序读取操作具有较好的性能。
缺点
- 读取放大:在读取数据时,可能需要在多个 SSTable 中进行查找,尤其是当数据分布在多个层次的 SSTable 中时,会增加磁盘 I/O 操作的次数,导致读取性能下降。
- 合并操作开销:合并操作需要占用一定的系统资源,包括 CPU 时间和磁盘 I/O 带宽。在高并发写入的情况下,合并操作可能会影响系统的整体性能。
- 内存占用:MemTable 需要占用一定的内存空间来存储数据。如果写入量很大,可能需要较大的内存来保证系统的性能。