LSM Tree

LSM Tree原理

在正式介绍LSM Tree之前,我想先提出一个问题的答案:为什么需要LSM Tree ?

LSM Tree的主要应用领域是磁盘KV存储。在传统的数据结构,如B+树中,其大多的特点都是具有非常良好的读性能,其都是应对多读少写的应用场景。但是由于磁盘寻道时间以及磁头移动等因素,其没有良好的写性能。所以这就引入了LSM Tree这种数据结构。

LSM Tree全程是Log Structed Merge Tree。中文名字叫做日志结构的合并树,从名字上我们可以大概可以推断出以下几点,其也是LSM Tree的主要工作原理。

  1. 其和我们的日志设计有着异曲同工之妙
  2. 其是树结构
  3. 其操作过程中伴有合并操作

我们来回顾一个日志的设计。就拿系统日志举例,其每次写日志都是追加这个日志信息到整个日志文件的尾部,这样我们写文件的性能就是O(1)了。
在这里插入图片描述
LSM Tree就是采用的这样的设计思想,不过其存储的就是KV键值对。但是这样必不可免的就会遇到问题,假如后面我们把a这个key设置为2,那么我们就会把<a,2,2>这样的键值对继续插入到我们的文件中。这样我们需要从后至前遍历,因为原来的数据<a,1,1>是过期的,我们想要拿到的是最新的数据。
在这里插入图片描述
这样会带来两个问题:

  1. 无限的插入数据会导致数据的无限增长,旧数据需要删除
  2. 这样会导致我们的搜索效率是O(n)

LSM Tree的归并操作

首先我们先来简单介绍一下如何去抑制数据的无限增长。对于过期的数据起始我们很容易就想到GC垃圾回收机制。但是这里我们要注意几个点:

  1. GC不能妨碍正常的进程逻辑,所以最好是另起一个进程来完成GC
  2. 对于同一个文件如果一边往里面插入数据,一边进行Merge操作,这样就会导致数据的 “线程安全” 问题。所以我们需要对文件进行分段,对于新输入的数据放到当前文件,这个是GC不许回收的。当这个“当前文件”的内容达到一个阈值,其就会被标记成旧文件。之后的新数据会再开一个文件存储。
    在这里插入图片描述
    那么整个GC是怎么回收空间的呢?

我们拿上图的三个文件以及LevelDB的应用举例。整个LevelDB分为L0~L6总共七层存储空间,每一层的空间都是上一层的10倍。当空间占用达到阈值的时候,就会触发归并操作。假设上面的三个文件都处于L0层,其中文件A的的Key范围是[a,e],文件B的范围是[a,i],文件C的范围是[b,z] 。可以很清楚的看到文件A、B、C互有重叠,这就会引发L0层的归并,使这三个文件归并到L1,L0的原有数据也就消失了。

在这里插入图片描述
那么根据这个结构,我们很容易就推断出一个规律:除了L0层之外,其他层之内的Key都是唯一的,其他层之间的Key不保证唯一性且越上层的Key越代表最新数据

LevelDB中的应用

其实上面讲的也就是LevelDB中SLM Tree的使用了。但是除了硬盘之上,LevelDB还在内存上做了优化:对于一个数据源的数据来说,其首先会用流式在内存中写入,形成MemTable,同时,为了能够恢复文件,其还会写入日志文件LOG。之后,当这个MemTable的大小达到阈值之后,这些数据会被以批量的形式形成IMemTable,同时也会落地到磁盘中(这个时候会清理LOG中上一批的数据)。这个时候如果还有数据写入,就写入一个新的MemTable中,如此循环。

在这里插入图片描述

LSM Tree的查询

根据上图我们可以看到,数据不仅在内存中出现,还在磁盘中出现。首先,对于一个Key来说,如果我们在内存的MemTable中找到,那就直接返回了,因为内存中的Key肯定是最新的。但是如果我们没找到,就需要去磁盘了,根据LSM Tree 的建立规则,我们很容易知道,越上层的数据越新。同时每一层每个文件都有这个文件的Key范围,那么我们就可以利用布隆过滤器在获得常量级的查询效率。

参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
LSM Tree是一种高效的键值存储结构,它可以在磁盘上存储大量的数据,并且具有快速的读写性能。下面是一个用Java实现的LSM Tree的示例代码: ``` import java.util.*; public class LSMTree<K extends Comparable<K>, V> { private final int MAX_LEVELS = 10; private final int MAX_SIZE = 1000000; private List<TreeMap<K, V>> levels; private int size; public LSMTree() { levels = new ArrayList<>(); for (int i = 0; i < MAX_LEVELS; i++) { levels.add(new TreeMap<>()); } size = 0; } public void put(K key, V value) { levels.get(0).put(key, value); size++; if (size > MAX_SIZE) { merge(); } } public V get(K key) { for (int i = 0; i < levels.size(); i++) { TreeMap<K, V> level = levels.get(i); if (level.containsKey(key)) { return level.get(key); } } return null; } private void merge() { List<TreeMap<K, V>> newLevels = new ArrayList<>(); newLevels.add(new TreeMap<>()); for (int i = 0; i < levels.size(); i++) { TreeMap<K, V> level = levels.get(i); if (level.size() > MAX_SIZE) { newLevels.add(new TreeMap<>()); for (Map.Entry<K, V> entry : level.entrySet()) { newLevels.get(newLevels.size() - 1).put(entry.getKey(), entry.getValue()); } } else { for (Map.Entry<K, V> entry : level.entrySet()) { newLevels.get(newLevels.size() - 1).put(entry.getKey(), entry.getValue()); } } } levels = newLevels; size = levels.get(0).size(); } } ``` 这个实现使用了一个列表来存储多个TreeMap,每个TreeMap代表一个不同的层级。当插入的数据量超过了一个阈值时,会进行层级合并操作,将多个TreeMap合并成一个更大的TreeMap。在查询时,会从最高层级开始查找,如果找到了就直接返回,否则继续向下查找。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值