mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-19 23:53:48 +08:00
fix img
This commit is contained in:
@@ -215,10 +215,10 @@ function hide_canvas() {
|
||||
<p>可以看到双树操作是比较简单明了的,而且可以作为一种 B 树类的索引结构而存在。但实际上几乎没有存储引擎去使用它,主要原因是它的合并操作是同步的,也就是刷盘的时候要同步进行合并。而刷盘本身是个相对频繁的操作,这样会造成写放大,也就是会影响写入效率且会占用非常大的磁盘空间。</p>
|
||||
<p><strong>多树结构是在双树的基础上提出的,内存数据刷盘时不进行合并操作</strong>,而是完全把内存数据写入到单独的文件中。那这个时候另外的问题就出现了:随着刷盘的持续进行,磁盘上的文件会快速增加。这时,读取操作就需要在很多文件中去寻找记录,这样读取数据的效率会直线下降。</p>
|
||||
<p>为了解决这个问题,此种结构会引入合并操作(Compaction)。该操作是异步执行的,它从这众多文件中选择一部分出来,读取里面的内容而后进行合并,最后写入一个新文件中,而后老文件就被删除掉了。如下图所示,这就是典型的多树结构合并操作。而这种结构也是本讲介绍的主要结构。</p>
|
||||
<p><img src="assets/Cgp9HWAqWPaAI1cVAAF0GY8NUFc418.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAqWPaAI1cVAAF0GY8NUFc418.png" alt="png" /></p>
|
||||
<p>最后,我再为你详细介绍一下刷盘的流程。</p>
|
||||
<p>首先定义几种角色,如下表所示。</p>
|
||||
<p><img src="assets/Cgp9HWAqWQKAYYdQAAChiD3W3lQ653.png" alt="2.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAqWQKAYYdQAAChiD3W3lQ653.png" alt="png" /></p>
|
||||
<p>数据首先写入当前内存表,当数据量到达阈值后,当前数据表把自身状态转换为刷盘中,并停止接受写入请求。此时会新建另一个内存表来接受写请求。刷盘完成后,由于数据在磁盘上,除了废弃内存表的数据外,还对提交日志进行截取操作。而后将新数据表设置为可以读取状态。</p>
|
||||
<p>在合并操作开始时,将被合并的表设置为合并中状态,此时它们还可以接受读取操作。完成合并后,原表作废,新表开始启用提供读取服务。</p>
|
||||
<p>以上就是经典的 LSM 树的结构和一些操作细节。下面我们开始介绍如何对其进行查询、更新和删除等操作。</p>
|
||||
@@ -236,14 +236,14 @@ function hide_canvas() {
|
||||
<p>常见的合并策略有 Size-Tiered Compaction 和 Leveled Compaction。</p>
|
||||
<h4>Size-Tiered Compaction</h4>
|
||||
<p>下图就是这种策略的合并过程。</p>
|
||||
<p><img src="assets/CioPOWAqWQ6AH7acAACcL3NKUVQ048.png" alt="3.png" /></p>
|
||||
<p><img src="assets/CioPOWAqWQ6AH7acAACcL3NKUVQ048.png" alt="png" /></p>
|
||||
<p>其中,数据表按照大小进行合并,较小的数据表逐步合并为较大的数据表。第一层保存的是系统内最小的数据表,它们是刚刚从内存表中刷新出来的。合并过程就是将低层较小的数据表合并为高层较大的数据表的过程。Apache Cassandra 使用过这种合并策略。</p>
|
||||
<p>该策略的优点是比较简单,容易实现。但是它的空间放大性很差,合并时层级越高该问题越严重。比如有两个 5GB 的文件需要合并,那么磁盘至少要保留 10GB 的空间来完成这次操作,可想而知此种容量压力是巨大的,必然会造成系统不稳定。</p>
|
||||
<p>那么有没有什么策略能缓解空间放大呢?答案就是 Leveled Compaction。</p>
|
||||
<h4>Leveled Compaction</h4>
|
||||
<p>如名称所示,该策略是将数据表进行分层,按照编号排成 L0 到 Ln 这样的多层结构。L0 层是从内存表刷盘产生的数据表,该层数据表中间的 key 是可以相交的;L1 层及以上的数据,将 Size-Tiered Compaction 中原本的大数据表拆开,成为多个 key 互不相交的小数据表,每层都有一个最大数据量阈值,当到达该值时,就出发合并操作。每层的阈值是按照指数排布的,例如 RocksDB 文档中介绍了一种排布:L1 是 300MB、L2 是 3GB、L3 是 30GB、L4 为 300GB。</p>
|
||||
<p>该策略如下图所示。</p>
|
||||
<p><img src="assets/Cgp9HWAqWRmAPoPlAACQe1Ek6yI202.png" alt="4.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAqWRmAPoPlAACQe1Ek6yI202.png" alt="png" /></p>
|
||||
<p>上图概要性地展示了从 L1 层开始,每个小数据表的容量都是相同的,且数据量阈值是按 10 倍增长。即 L1 最多可以有 10 个数据表,L2 最多可以有 100 个,以此类推。</p>
|
||||
<p>随着数据表不断写入,L1 的数据量会超过阈值。这时就会选择 L1 中的至少一个数据表,将其数据合并到 L2 层与其 key 有交集的那些文件中,并从 L1 中删除这些数据。</p>
|
||||
<p>仍然以上图为例,一个 L1 层数据表的 key 区间大致能够对应到 10 个 L2 层的数据表,所以一次合并会影响 11 个文件。该次合并完成后,L2 的数据量又有可能超过阈值,进而触发 L2 到 L3 的合并,如此往复。</p>
|
||||
|
||||
Reference in New Issue
Block a user