LSM-Tree关键技术
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
LSM-Tree关键技术
1.0、哪里用到了LSM-Tree
最初看到LSM-Tree这个树结构,是从友情站点NoSQLFan上一篇介绍有着高性能key-value的数据库nessDB的文章内了解到的。nessDB是一个小巧、高性能、可嵌入式的key/value存储引擎,使用标准C开发,支持Linux, *BSD, OS X and Solaris 等系统,无第三方库依赖。同时nessDB还提供一个服务端,支持Redis
的 PING, SET, MSET, GET, MGET, DEL, EXISTS, INFO, SHUTDOWN命令,你可以使用任何一款Redis客户端来连接和操作nessDB。
而整个引擎基于LSM-Tree思想开发,对随机写非常友好。为提高随机读,nessDB使用了Level LRU和Bloom Filter策略。我们知道,现在一般主流的数据库索引一般都是用的B/B+树系列,包括MySQL及NoSQL中的MongoDB。而这个nessDB为何例外,LSM-Tree有何特别呢?抱着对它的好奇,便研究学习了下此LSM-Tree。
甚至包括现在另一种比较火/流行的数据库Cassandra 以及与众多类BigTable存储一样,都采用的是LSM-Tree 结构来存储数据,简单来说就是将原来的直接维护索引树变为增量写的方式,这样能够保证对磁盘的操作是顺序的。
再后来,看到了一篇论文:The Log-Structured Merge-Tree,这篇论文原英文有30多页,看了两个下午。下面,本文第一部分就结合这篇论文及星星的译作,从最基本的LSM-Tree的C0C1两组件算法,谈到多组件算法,让读者对LSM-Tree这个树结构的原理有个充分的认识与理解。
1.1、什么是LSM-Tree
相信随着NoSQL据库,尤其是类BigTable系统的流行,LSM-Tree这个树结构,大家很快就不会再如此时这般陌生了。blog 内已经详细阐述过B/B+树,那么这个LSM跟B树系列相比,有什么不同呢,它的优势在哪,适用于何种情况?一切,请听我慢慢道来。
此处的Log-Structured这个词源于Ousterhout和Rosenblum在1991年发表的经典论文
<
LSM-Tree具体是一种什么样的树结构呢,具体来说,LSM-Tree通过使用某种算法(两组件C0C1及多组件算法),对索引变更进行延迟及批量处理,并通过一种类似于归并排序的方式高效地将更新迁移到磁盘。它使得我们可以使用更少的磁盘运动来执行在Acct-ID||Timestamp上的频繁插入操作。
将索引节点放置到磁盘上的这一过程进行延迟处理,是最根本的,LSM-Tree结构通常就是包含了一系列延迟放置机制。LSM-Tree结构也支持其他的操作,比如删除,更新,甚至是那些具有long latency的查询操作。只有那些需要立即响应的查询会具有相对昂贵的开销。LSM-Tree的主要应用场景就是,查询频率远低于插入频率的情况(大多数人不会像开支票或存款那样经常查看自己的账号活动信息)。在这种情况下,最重要的是降低索引插入开销;与此同时,也必须要维护一个某种形式的索引,因为顺序搜索所有记录是不可能的。
因此,LSM-Tree最适用于那些索引插入频率远大于查询频率的情况,比如,对于历史记录表和日志文件来说,就属于这种情况。OK,接下来,咱们就来截杀这个两组件C0C1算法,以及多组件算法。
1.2、LSM-Tree之两组件C0C1算法
1.2.1、LSM-Tree两组件COC1
由上文,我们已经知道,LSM-Tree通过使用某种算法(两组件C0C1及多组件算法),对索引变更进行延迟及批量处理,并通过一种类似于归并排序的方式高效地将更新迁移到磁盘。更进一步,LSM-Tree由两个或多个类树的数据结构组件构成。我们先考虑简单的两个组件的情况,如下图所示:
如上,便是LSM-Tree之两组件C0C1算法的示意图。C1树在左边,存在于磁盘Disk上,C0树在右边,存在于内存Memory 上。
在每条历史记录表中的记录生成时,会首先向一个日志文件中写入一个用于恢复该插入操作的日志记录。然后针对该历史记录表的实际索引节点会被插入到驻留在内存中的C0树,之后它将会在某个时间从右到左被移到磁盘上的C1树中。对于某条记录的检索,将会首先在C0中查找,然后是C1。在记录从C0移到C1中间肯定存在一定时间的延迟,这就要求能够恢复那些crash之前还未被移出到磁盘的记录。
向驻留在内存中的C0树插入一个索引条目不会花费任何IO开销。但是,用于保存C0的内存的成本要远高于磁盘,这就限制了它的大小。这就需要一种有效的方式来将记录迁移到驻留在更低成本的存储设备上的C1树中。为了实现这个目的,在当C0树因插入操作而达到接近某个上限的阈值大小时,就会启动一个rolling merge过程,来将某些连续的记录段从C0树中删除,并merge到磁盘上的C1树中。如下图所示:
如上,Rolling merge实际上由一系列的merge步骤组成。首先会读取一个包含了C1树中叶节点的multi-page block,这将会使C1中的一系列记录进入缓存。之后,每次merge将会直接从缓存中以磁盘页的大小读取C1的叶节点,将那些来自于叶节点的记录与从C0树中拿到的叶节点级的记录进行merge,这样就减少了C0的大小,同时在C1树中创建了一个新的merge好的叶节点。
磁盘上的C1树具有一个类似于B-树的目录结构,但是它是为顺序性的磁盘访问优化过的,所有的节点都是100%满的,同时为了有效利用磁盘,在根节点之下的所有的单页面节点都会被打包(pack)放到连续的多页面磁盘块(multi-page block)上;类似的优化也被用在SB-树中。对于rolling merge和长的区间检索的情况将会使用multi-page block io,而在匹配性的查找中会使用单页面节点以最小化缓存需求。对于root之外的节点使用256Kbytes的multi-page block大小(root节点根据定义通常都只是单个的页面)。
1.2.2、LSM-Tree之内存上C0树的选择
LSM-tree从诞生那一刻开始的整个变化过程如下,我们首先从针对C0的第一次插入开始。与C1树不同,C0树不一定要具有一个类B-树的结构。首先,它的节点可以具有任意大小:没有必要让它与磁盘页面大小保持一致,因为C0树永不会位于磁盘上(位于哪?内存上阿),因此我们就没有必要为了最小化树的深度而牺牲CPU的效率(如果看下B-树,就可以知道实际上它为了降低树的高度,牺牲了CPU效率。所以,在当整个数据结构都是在内存中时(别忽略了这个前提),一棵普通的2-3树(2-3-4树和B树的前身)或AVL树足矣,且不必使用B树查找,因为B树更适合外存查找(当然,在B树查找数据时,把数据从磁盘导入到内存后,由于B树表的结构是有序的,可以直接二分查找)。