这是我参与「第四届青训营 」笔记创作活动的第十四天
LSMT,即Log-Structured Merge-Tree,这是一个经典的数据结构,在大数据系统中有着非常广泛的应用。很多耳熟能详的经典系统,底层就是基于LSMT实现的。
背景
B+ Tree和B Tree的最大区别是将所有数据都放在了叶子节点,从而优化了批量插入和批量查询的效率,而优化的核心逻辑就是无论是什么存储介质,顺序存储的效率一定优于随机存储。
直观优化
既然随机读比顺序读性能差这么多,如果能发明一个数据结构能充分地利用上这一点该多好。
一个朴素的想法是将所有的读写都设计成顺序读写,比如日志系统,在写入时,总是在文件末尾追加,但如果要把读写都设计成顺序的,意味着在查找时,必须要读入文件中的所有内容。
这个思路应用最广的地方有两个:一个是数据库日志,在数据库执行写入或修改操作的时候,把所有操作都记录的binlog;还有一处是消息中间件,如Kafka。
但在复杂的增删改查场景中,尤其是涉及到批量读写场景,简单的文件顺序读写就不能满足需求了。当然我们可以用hash表或B+树,但这些复杂的数据结构都避免不了比较慢的随机读写操作,而我们希望随机读写尽量减少。这是基于这个原理,LSMT被发明出来了,LSMT使用了一种独特的机制,牺牲了一些读操作的性能,保证了写操作的能力,他能够让所有的操作顺序化,几乎完全避免了随机读写。
LSMT的增删改查
LSMT的实现原理
LSMT的原理很简单,本质上就是在SSTable基础上增加了一个MemTable,MemTable顾名思义,就是存放在内存中的数据结构,可以快速实现增删改查,比如红黑树,Skiplist都行。其次,我们还需要一个log文件,和数据库的binlog相当,记录数据发生的变化,用于服务器宕机时找回数据。
LSMT的整体架构如下:
查找
当要查找一个元素的时候,会先查找MemTable,因为他在内存中,不需要读文件,如果MemTable中没找到,就去一个一个SSTable来查找,由于SSTable也是顺序存储的,可以用二分法查找,所以查询速度会比较快。
但是,如果SSTable文件数量可能会很多,而且我们必须要顺序查找,所以当SSTable数多时,会影响查找速度。为了解决这个上网呢提,我们可以引入布隆过滤器进行优化:我们对每一个SSTable建立一个布隆过滤器,可以快速地判断元素是否在某一个SSTable中。布隆过滤器判断元素不存在是一定准确的,而判断存在可能会有一个很小的几率失误,但这个失误的几率是可以控制的额,我们可以设置合理的参数,使得失误率足够低。
增删改
除了查找外,增删改都发生在MemTable中,比如当我们要增加一个元素的时候,我们直接增加在Memtable中,而不是写入文件,这样保证了高性能。
修改和删除也一样,如果需要修改的元素刚好在MemTable中,那没什么好说的,我们直接进行修改,那如果不在MemTable中,我们如果先找到,再修改,免不了要做磁盘读写,会大大消耗性能,所以我们还在Memtable中进行操作,我们会插入这个元素,然后标记成修改或者删除。
所以我们可以把增删改这三个操作看成是添加,但这样会带来一个问题,会导致很快Memtable中就积累了大量的数据,而我们的内存资源也是有限的,不能无限增长,为了解决这个问题,需要定期将memtable种的内容存储到磁盘,存储成一个SSTable。这就是为什么要设计SSTable,SSTable是LSMT落盘产生的。
同样,由于我们不断地落盘,同样也会导致SSTable文件数量的增加,如前面分析,SSTable数量多,会影响我们的查询性能,所以不能放任SSTable无限制增加。再加上我们存储了许多修改和删除的信息,我们需要把这些信息落实。为了达成这点,我们需要定期将所有的SSTable合并,在合并的过程中,完成数据的删除及修改工作。换句话说,之前的删除、修改只是被记录下来,直到合并的时候才真正执行。