文章目录
- 前言
- 核心思想
- LSM 树的结构
- LSM 树原理
- LSM 树的读写
- LSM 树读写架构图
- 写入操作
- 删除操作
- 更新操作
- 查询操作
- 合并操作
前言
LSM 树,即日志结构合并树(Log-Structured Merge-Tree)是Google BigTable 和 HBase 的基本存储算法,它是传统关系型数据库的 B+ 数的改进。算法的关注重心是 “如何在频繁的数据改动下保持系统读取速度的稳定性”,算法的核心在于尽量保证数据是顺序存储到磁盘上的,并且会有频繁地对数据进行整理,保证其顺序性。而顺序性就可以最大程度保证数据的读取性能稳定。
核心思想
LSM树核心就是放弃部分读能力,换取写入的最大化能力。LSM 树会将所有的数据插入、修改、删除等操作保存在内存中,当此类操作达到一定得数据量后,再批量地写入磁盘当中。而在写磁盘时,会和以前的数据做合并。在合并过程中,并不会像 B+ 树一样,在原数据的位置上修改,而是直接插入新的数据, 从而避免了随机写。
LSM 树的结构
LSM 树原理
LSM树由两个或以上的存储结构组成,比如在论文中为了方便说明使用了最简单的两个存储结构。一个存储结构常驻内存中,称为C0 tree,具体可以是任何方便健值查找的数据结构,比如红黑树、map之类,甚至可以是跳表。 另外一个存储结构常驻在硬盘中,称为C1 tree,具体结构类似B树。C1所有节点都是100%满的,节点的大小为磁盘块大小。
LSM 树的读写
LSM 树读写架构图
LSM树的结构是横跨内存和磁盘的,包含memtable
、immutable memtable
、SSTable
等多个部分。
- memtable
顾名思义,memtable是在内存中的数据结构,用以保存最近的一些更新操作,当写数据到memtable中时,会先通过WAL的方式备份到磁盘中,以防数据因为内存掉电而丢失。memtable可以使用跳跃表或者搜索树等数据结构来组织数据以保持数据的有序性。当memtable达到一定的数据量后,memtable会转化成为immutable memtable,同时会创建一个新的memtable来处理新的数据。 - immutable memtable
顾名思义,immutable memtable在内存中是不可修改的数据结构,它是将memtable转变为SSTable的一种中间状态。目的是为了在转存过程中不阻塞写操作。写操作可以由新的memtable处理,而不用因为锁住memtable而等待。 - SSTable
SSTable(Sorted String Table)即为有序键值对集合,是LSM树组在磁盘中的数据的结构。如果SSTable比较大的时候,还可以根据键的值建立一个索引来加速SSTable的查询。下图是一个简单的SSTable结构示意:
写入操作
写操作首先需要通过WAL将数据写入到磁盘Log中,防止数据丢失,然后数据会被写入到内存的memtable中,这样一次写操作即已经完成了,只需要1次磁盘IO,再加1次内存操作。相较于B+树的多次磁盘随机IO,大大提高了效率。随后这些在memtable中的数据会被批量的合并到磁盘中的SSTable当中,将随机写变为了顺序写。
删除操作
当有删除操作时,并不需要像B+树一样,在磁盘中的找到相应的数据后再删除,只需要在memtable中插入一条数据当作标志,如delKey:1933,当读操作读到memtable中的这个标志时,就会知道这个key已被删除。随后在日志合并中,这条被删除的数据会在合并的过程中一起被删除。
更新操作
更新操作和000删除操作类似,都是只操作memtable,写入一个标志,随后真正的更新操作被延迟在合并时一并完成。
查询操作
查询操作相较于B+树就会很慢了,读操作需要依次读取memtable、immutable memtable、SSTable0、SSTable1…。需要反序地遍历所有的集合,又因为写入顺序和合并顺序的缘故,序号小的集合中的数据一定会比序号大的集合中的数据新。所以在这个反序遍历的过程中一旦匹配到了要读取的数据,那么一定是最新的数据,只要返回该数据即可。但是如果一个数据的确不在所有的数据集合中,则会白白得遍历一遍。
读操作看上去比较笨拙,所幸可以通过布隆过滤器来加速读操作。当布隆过滤器显示相应的SSTable中没有要读取的数据时,就跳过该SSTable。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
还有上面提到的索引文件,也可以加速读操作。
合并操作
由前面的增删改查操作来看,合并操作是LSM树最重要的操作。
合并操作有两个主要的作用:
合并内存中的数据到磁盘中。
由于将内存数据合并到磁盘当中会产生大量的小的集合,并且更新和删除操作会产生大量的冗余数据,通过合并操作可以减少集合中的冗余数据并降低读操作时线性扫描的耗时。