树 Story —— LSM 日志结构合并树

LSM树是一种为解决磁盘IO效率问题而设计的数据结构,常见于NoSQL数据库。它通过内存中的MemTable和磁盘上的SSTable实现数据的高效写入,利用WAL保证数据安全。在写场景下,LSM树性能优越,但在读场景下相对较慢,适合写多读少的场景。数据删除时标记为已删除,合并时实际删除。代表数据库有NessDB、Leveldb和HBase。

LSM 不是 老色批

LSM 树 (Log-Structured Merge-Tree) 即日志结构合并树。其实它并不属于一个具体的数据结构,它更多是一种数据结构的设计思想。大多 NoSQL 数据库核心思想都是基于 LSM 来做的,只是具体的实现不同。

何为 LSM 树

由于磁盘 IO 的开销是数据库效率瓶颈之一,因此产生了很多减少磁盘 IO 的方案。而 LSM 树就是为了解决频繁磁盘读写的方案之一。

使用 B 树之类的多路查找树索引数据时,由于插入数据会导致进行再平衡,使插入的效率变低。同时,由于 B 树的叶子节点之间的数据可能是相邻的,但是由于处于不同子树,导致虽然是数值是连续的,但是物理不连续,造成了大量的随机读写,使磁盘 IO 效率变低,

LSM 提供了一种设计思想,同时使用内存和磁盘存储数据。并通过一定的策略,将内存的数据按顺序刷到磁盘中。这样数据在磁盘中从逻辑和物理上都是有序存储的。

LSM 在数据写入时,不直接写到磁盘中,而是在内存中保留一段时间,并使用 WAL(Write-ahead logging,预写式日志)来保证数据不丢失,类似 MySQL 中的 Redo Log。这使得 LSM 可以承受很高的写请求。

1

LSM 的基本原理

LSM 中包含三个部分(以 RocksDB 为例,不同的存储引擎可能设计思想相同,具体实现方式不同)

  1. MemTable 内存中的数据结构
  2. Immutable MemTable 即将转为 SSTable 的中间状态
  3. SSTable(Sorted String Table) LSM 树在磁盘中的数据结构

新的数据在内存中以 MemTable 的形式组织起来,当达到一定容量后,转为 Immutable MemTable ,使其与 SSTable 中的数据进行合并。这时再有新的数据插入时,则生成新的 MemTable 进行记录。Immutable MemTable 与 SSTable 合并后,按照一定顺序存储到磁盘中。

当查询数据时,先从 MemTable 中查找,如果查找不到,则到磁盘中查找。所以 LSM 树的查询成本很高。

由于使用 WAL 日志记录数据的操作,所以当内存中的数据由于意外丢失后,可以通过日志重建内存数据。WAL 日志虽然存在了磁盘,但是也是按顺序追加存储,所以效率很高,不影响插入数据的效率。

插入与合并

在实际的应用中,内存中会有很多 MemTable ,而磁盘中也会有很多 SSTable,我们假设内存中只有一个 MemTable,称之为 C0,磁盘中也只有一个 SSTable ,称之为 C1。

在合并的过程中,有两个块结构。

  1. Emptying Block 存放 C1 中未合并的数据
  2. Filling Block 存放合并后的数据,装满数据后,将数据追加到磁盘中

插入数据时,先将数据放到内存组织起来,等到合适的时机,将内存的数据和磁盘的数据进行合并,并顺序写到磁盘上。

下面将以大量的图文的方式,简述插入、合并、追加的过程。

插入数据 1

2

现在日志里面记录插入日志

3

然后将数据 1 放入 C0

4

插入数据 2

5

插入数据 3,C0 以 B 树的形式存储(用其他数据结构也可以)

接着插入其他一坨数据

7

这时假定触发了合并,那么我们首先将 C1 的数据加载到 Emptying Block 中,但是由于 C1 没有数据,所以 Emptying Block 数据为空。直接将 C0 中的数据按顺序放到 Filling Block 中即可。

将节点 1 放入 Filling Block

节点 1 放到 Filling Block 后,则将 C0 中的节点 1 删除

再放入节点 2,由于删除节点 2 后,导致不平衡,需要将树重新平衡。

节点 3 替换到 节点 2的位置,保持树的平衡

将节点 3 放入 Filling Block 后,将树「左旋」保持树的平衡 (左旋的方法参考平衡二叉树的介绍)

当 Filling Block 到达容量限制后,将 Filling Block 的内容追加到 C1 中

14

然后我们再插入数据 4 9 10

15

当现在又要进行合并的时候,则需要将 C1 的数据放到 Emptying Block 里面,然后与 C0 进行合并。

17

将 Emptying Block 的数据与 C0 的数据按顺序合并到 Filling Block。Emptying Block 的数据合并到 Filling Block 后,将 Emptying Block 的数据删除。

Filling Block 的数据满了之后,追加到 C1,删除原来的数据

22

然后再继续合并

Filling Block 满了之后,在追加到 C1 上,并构建 B 树 (或其他索引结构)

这样通过不停的插入、合并、追加,最终构成了 LSM 树的设计思想。

查找数据

查找数据时,先从内存中进行查找,如果内存中没有,则到磁盘中查找。

查找 9,在 内存中可以命中

27

查找 7,在内存中找不到,再去磁盘中查找

删除数据

删除数据时,LSM 树将节点标记为「已删除」状态,而不直接删除。等到下次合并操作时,才将数据真正删除。

如果数据在内存里,则直接标记为「已删除」

如果数据在磁盘里,去磁盘里查找并删除成本太高了。所以直接在内存里插入一个一样的数据,并标记为「已删除」。

等到下次 合并 的时候,遇到已删除的节点,则直接删除即可。

总结

LSM 利用内存的高性能 IO,提高了写的性能。利用合并策略,将内存的数据与磁盘的数据进行合并排序并持久化。通过 WAL 来防止内存数据因为断点等意外丢失。软硬兼施,使得 LSM 拥有了良好的读写性能。

由于 LSM 查询时,先查询内存,再查询磁盘,而且内存和磁盘不一定只有一棵树,往往存在众多树结构,这导致查询的性能远远不如 B+ 树等传统关系型存储引擎快。

LSM 树在写场景下,远超 B+ 树,而在读场景下,远远落后 B+ 树。所以 LSM 适合应用在写多读少的场景。

代表数据库:NessDB、Leveldb、HBase等

核心思想的核心就是放弃部分读能力,换取写入的最大化能力,放弃磁盘读性能来换取写的顺序性。极端的说,基于 LSM 树实现的 HBase 的写性能比 MySQL 高了一个数量级,读性能低了一个数量级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值