看图轻松理解数据结构与算法系列(NoSQL存储-LSM树)

前言

推出一个新系列,《看图轻松理解数据结构和算法》,主要使用图片来描述常见的数据结构和算法,轻松阅读并理解掌握。本系列包括各类堆、各类队列、各类列表、各类树、各类图、各类排序等等几十篇的样子。mysql

关于LSM树

LSM树,即日志结构合并树(Log-Structured Merge-Tree)。其实它并不属于一个具体的数据结构,它更可能是一种数据结构的设计思想。大多NoSQL数据库核心思想都是基于LSM来作的,只是具体的实现不一样。因此原本不打算列入该系列,可是有朋友留言了好几回让我讲LSM树,那么就说一下LSM树。算法

LSM树诞生背景

传统关系型数据库使用btree或一些变体做为存储结构,能高效进行查找。但保存在磁盘中时它也有一个明显的缺陷,那就是逻辑上相离很近但物理却可能相隔很远,这就可能形成大量的磁盘随机读写。随机读写比顺序读写慢不少,为了提高IO性能,咱们须要一种能将随机操做变为顺序操做的机制,因而便有了LSM树。LSM树能让咱们进行顺序写磁盘,从而大幅提高写操做,做为代价的是牺牲了一些读性能。sql

关于磁盘IO

磁盘读写时涉及到磁盘上数据查找,地址通常由柱面号、盘面号和块号三者构成。也就是说移动臂先根据柱面号移动到指定柱面,而后根据盘面号肯定盘面的磁道,最后根据块号将指定的磁道段移动到磁头下,即可开始读写。数据库

整个过程主要有三部分时间消耗,查找时间(seek time) +等待时间(latency time)+传输时间(transmission time) 。分别表示定位柱面的耗时、将块号指定磁道段移到磁头的耗时、将数据传到内存的耗时。整个磁盘IO最耗时的地方在查找时间,因此减小查找时间能大幅提高性能。网络

LSM树原理

LSM树由两个或以上的存储结构组成,好比在论文中为了方便说明使用了最简单的两个存储结构。一个存储结构常驻内存中,称为C0 tree,具体能够是任何方便健值查找的数据结构,好比红黑树、map之类,甚至能够是跳表。另一个存储结构常驻在硬盘中,称为C1 tree,具体结构相似B树。C1全部节点都是100%满的,节点的大小为磁盘块大小。数据结构

image

插入步骤

大致思路是:插入一条新纪录时,首先在日志文件中插入操做日志,以便后面恢复使用,日志是以append形式插入,因此速度很是快;将新纪录的索引插入到C0中,这里在内存中完成,不涉及磁盘IO操做;当C0大小达到某一阈值时或者每隔一段时间,将C0中记录滚动合并到磁盘C1中;对于多个存储结构的状况,当C1体量愈来愈大就向C2合并,以此类推,一直往上合并Ck。并发

image

合并步骤

合并过程当中会使用两个块:emptying block和filling block。app

  1. 从C1中读取未合并叶子节点,放置内存中的emptying block中。
  2. 从小到大找C0中的节点,与emptying block进行合并排序,合并结果保存到filling block中,并将C0对应的节点删除。
  3. 不断执行第2步操做,合并排序结果不断填入filling block中,当其满了则将其追加到磁盘的新位置上,注意是追加而不是改变原来的节点。合并期间如故宫emptying block使用完了则再从C1中读取未合并的叶子节点。
  4. C0和C1全部叶子节点都按以上合并完成后即完成一次合并。

关于优化措施

本文用图阐述LSM的基本原理,但实际项目中其实有不少优化策略,并且有不少针对LSM树优化的paper。好比使用布隆过滤器快速判断key是否存在,还有作一些额外的索引以帮助更快找到记录等等。机器学习

插入操做

向LSM树中插入A E L R U,首先会插入到内存中的C0树上,这里使用AVL树,插入“A”,先项磁盘日志文件追加记录,而后再插入C0,异步

image

插入“E”,一样先追加日志再写内存,

image

继续插入“L”,旋转后以下,

image

插入“R”“U”,旋转后最终以下。

image

假设此时触发合并,则由于C1尚未树,因此emptying block为空,直接从C0树中依次找最小的节点。filling block长度为4,这里假设磁盘块大小为4。

开始找最小的节点,并放到filling block中,

image

继续找第二个节点,

image

以此类推,填满filling block,

image

开始写入磁盘,C1树,

image

继续插入B F N T,先分别写日志,而后插入到内存的C0树中,

image

假如此时进行合并,先加载C1的最左边叶子节点到emptying block,

image

接着对C0树的节点和emptying block进行合并排序,首先是“A”进入filling block,

image

而后是“B”,

image

合并排序最终结果为,

image

将filling block追加到磁盘的新位置,将原来的节点删除掉,

image

继续合并排序,再次填满filling block,

image

将filling block追加到磁盘的新位置,上一层的节点也要以磁盘块(或多个磁盘块)大小写入,尽可能避开随机写。另外因为合并过程可能会致使上层节点的更新,能够暂时保存在内存,后面在适当时机写入。

image

查找操做

查找整体思想是先找内存的C0树,找不到则找磁盘的C1树,而后是C2树,以此类推。

假如要找“B”,先找C0树,没找到。

image

接着找C1树,从根节点开始,

image

找到“B”。

image

删除操做

删除操做为了能快速执行,主要是经过标记来实现,在内存中将要删除的记录标记一下,后面异步执行合并时将相应记录删除。

好比要删除“U”,假设标为#的表示删除,则C0树的“U”节点变为,

image

而若是C0树不存在的记录,则在C0树中生成一个节点,并标为#,查找时就能再内存中得知该记录已被删除,无需去磁盘找了。好比要删除“B”,那么没有必要去磁盘执行删除操做,直接在C0树中插入一个“B”节点,并标为#。

image

-------------推荐阅读------------

个人开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)

为何写《Tomcat内核设计剖析》

个人2017文章汇总——机器学习篇

个人2017文章汇总——Java及中间件

个人2017文章汇总——深度学习篇

个人2017文章汇总——JDK源码篇

个人2017文章汇总——天然语言处理篇

个人2017文章汇总——Java并发篇


跟我交流,向我提问:

欢迎关注:

相关文章
相关标签/搜索