高吞吐、HBase的承载者 LSMT 学习笔记

一、简介

LSMT,即Log-Structured Merge-Tree,这是一个经典的数据结构,在大数据系统中有着非常广泛的应用。很多耳熟能详的经典系统,底层就是基于LSMT实现的。

1.1、背景知识

B+ Tree和B Tree的最大区别是将所有数据都放在了叶子节点,从而优化了批量插入和批量查询的效率,而优化的核心逻辑就是无论是什么存储介质,顺序存储的效率一定优于随机存储。

下面的图很好的描述了随机读和顺序读的性能差异:
在这里插入图片描述
这种差异不仅在机械硬盘上存储,在SSD上也一样存在。

1.2、直观优化

既然随机读比顺序读性能差这么多,如果能发明一个数据结构能充分地利用上这一点该多好。

一个朴素的想法是讲所有的读写都设计成顺序读写,比如日志系统,在写入时,总是在文件末尾追加,但如果要把读写都设计成顺序的,意味着在查找时,必须要读入文件中的所有内容。

这个思路应用最广的地方有两个:一个是数据库日志,在数据库执行写入或修改操作的时候,把所有操作都记录的binlog;还有一处是消息中间件,如Kafka。

但在复杂的增删改查场景中,尤其是涉及到批量读写场景,简单的文件顺序读写就不能满足需求了。当然我们可以用hash表或B+树,但这些复杂的数据结构都避免不了比较慢的随机读写操作,而我们希望随机读写尽量减少。这是基于这个原理,LSMT被发明出来了,LSMT使用了一种独特的机制,牺牲了一些读操作的性能,保证了写操作的能力,他能够让所有的操作顺序化,几乎完全避免了随机读写。

1.3、SSTable

在介绍LSMT之前,先了解些他的子结构SSTable。

SSTable的全称是Stored String Table,本质上就是一个KV结构的顺序排列的文件。如下图:
在这里插入图片描述
最基础的SSTable就是上图右侧部分,即key和value按照key值得大小排序,并存储在文件当中。当我们要查找某个key值对应的数据的时候,我们会将整个文件读入内存,进行查找。同样,写入也是如此,我们会将插入的操作在内存中进行,得到结果只会,直接覆盖原本的文件,而不会在文件中修改,因为这会牵扯到移动大量的数据。

如果文件的数据量过大,我们需要另外建立一个索引文件,存储不同key对应的offset, 方便我们在读取文件的时候,可快速查找到我们想要查找的SSTable文件。索引文件如上图左侧部分。

需要注意的是,SSTable是不可修改的,我们只会用新的SSTable来覆盖旧的,而不会再原来的基础上做修改。因为修改会涉及到随机读写,这是我们不想要的。

二、LSMT的增删改查

2.1、LSMT的实现原理

LSMT的原理很简单,本质上就是在SSTable基础上增加了一个MemTable,MemTable顾名思义,就是存放在内存中的数据结构,可以快速实现增删改查,比如红黑树,Skiplist都行。其次,我们还需要一个log文件,和数据库的binlog相当,记录数据发生的变化,用于服务器宕机时找回数据。

LSMT的整体架构如下:
在这里插入图片描述

2.2、查找

当要查找一个元素的时候,会先查找MemTable,因为他在内存中,不需要读文件,如果MemTable中没找到,就去一个一个SSTable来查找,由于SSTable也是顺序存储的,可以用二分法查找,所以查询速度会比较快。

但是,如果SSTable文件数量可能会很多,而且我们必须要顺序查找,所以当SSTable数多时,会影响查找速度。为了解决这个上网呢提,我们可以引入布隆过滤器进行优化:我们对每一个SSTable建立一个布隆过滤器,可以快速地判断元素是否在某一个SSTable中。布隆过滤器判断元素不存在是一定准确的,而判断存在可能会有一个很小的几率失误,但这个失误的几率是可以控制的额,我们可以设置合理的参数,使得失误率足够低。

加上了布隆过滤器后的查找操作是这样的:
在这里插入图片描述
上图的星星表示布隆过滤器,我们先通过布隆过滤器判断元素是否存在,再进行查找。

2.3、增删改

除了查找外,增删改都发生在MemTable中,比如当我们要增加一个元素的时候,我们直接增加在Memtable中,而不是写入文件,这样保证了高性能。

修改和删除也一样,如果需要修改的元素刚好在MemTable中,那没什么好说的,我们直接进行修改,那如果不在MemTable中,我们如果先找到,再修改,免不了要做磁盘读写,会大大消耗性能,所以我们还在Memtable中进行操作,我们会插入这个元素,然后标记成修改或者删除。

所以我们可以把增删改这三个操作看成是添加,但这样会带来一个问题,会导致很快Memtable中就积累了大量的数据,而我们的内存资源也是有限的,不能无限增长,为了解决这个问题,需要定期将memtable种的内容存储到磁盘,存储成一个SSTable。这就是为什么要设计SSTable,SSTable是LSMT落盘产生的。
在这里插入图片描述
同样,由于我们不断地落盘,同样也会导致SSTable文件数量的增加,如前面分析,SSTable数量多,会影响我们的查询性能,所以不能放任SSTable无限制增加。再加上我们存储了许多修改和删除的信息,我们需要把这些信息落实。为了达成这点,我们需要定期将所有的SSTable合并,在合并的过程中,完成数据的删除及修改工作。换句话说,之前的删除、修改只是被记录下来,直到合并的时候才真正执行。

在这里插入图片描述
整个的归并过程并不难,类似于归并排序中的归并操作,只是我们需要加上状态的判断。

三、总结

回顾一下LSMT的整个过程,虽然说是树,但是树形结构并不明显。对于查找操作,先查Memtable, 没有再查SSTable顺序往下找,这点和树形结构是一致的。从原理上看,简直有点简单粗暴过头,但从实际效果看,效果非常好,在HBase、Kudu当中有着广泛的应用。

我们对比下他和B+树,在B+树中,为了能快速读取而使用了多路平衡树,这样可以迅速找到对应key的节点。我们只需要读入节点当中的内容即可,但也正因为平衡树的结构,导致了我们在写入数据的时候,会引起树结构的变动,这也就涉及到多次文件的随机读写。当我们的数据吞吐量很大的时候,会带来巨大的开销。而LSMT则不然,我们读取的时候,效率比B+树要低,但对于大数据的写入支持得更好。在大数据场景中,许多对于数据的吞吐量有着很高的要求,比如消息系统,分布式存储等。这时候B+树就无能为力了,但是同样,如果我们需要保证查找的效率,那LSMT也不太合适,因此两者没有谁比谁更优,而是看针对的应用场景。

最后,关于LSMT,有很多个变种,其中比较著名的就是LevelDB,他在LSMT上做了一些改动,进一步提升了性能。

猜你喜欢

转载自blog.csdn.net/shijinghan1126/article/details/109308288