LSM 思想广泛应用在大数据生态圈中,被人们广泛知道包含了 Hbase 、kudu 、druid 的设计思想
(注:此文章根据自己的经验总结得出,如果错误之处欢迎指出,不胜感激)
综述
数据库中的数据我们大多数存储在磁盘上,无论传统的Hdd 还是固态硬盘,对磁盘数据的顺序读写速度都远高于随机读写。然而基于B+数的索引结构式违背上上磁盘基本特点的—它会需要较多的磁盘随机读写。然后2006年左右一盘使用LSM-tree的论文出现,并促使了随后两个大数据组件 hbase和cassandra 横空出市。
LSM-tree 最大特点是同时使用两部分类树的结构来存储数据,并同时提供查询。
其中一部分数据结构(c0树)存在内存缓存中(通常叫做memtable)中,负责接受新的数据插入和更新以及请求,并直接在内存中对数据进行排序;另一部分数据结构(C1树)存在于硬盘上(这部分通常叫做sstable),他们是由存在缓存中C0树冲写到磁盘而成的。(主要负责提供读操作,特点是有序且不可以被更改)

LSM-tree 的另一大特点是除了使用两部分类树的数据结构外,还会使用日志文件(通常叫做 commit log)来为数据恢复做保障。这三类数据结构写作顺序一般是:所有的新插入与更新操作首先被记录到commit log 中------该操作叫做WAL(write ahead log) 然后在写入到memtable,最后当达到一定条件时数据会从memtable冲入到sstable 中,并抛弃相关的log数据;memtable 与sstable 可同时提供查询;当memtable 出问题时,可从commit log 与sstable 中将memtable 中数据恢复。
一、hbase 应用 LSM-tree 架构
Hbase 的架构来体会其架构中基于LSM-tree 的部分特点。
按照WAL的原则,
第一步数据首先会写到Hbase的Hlog(相当于commit log)里,
第二部再写到Memstore(相当于memtable) 里,
第三部数据会冲写到磁盘storeFile(相当于sstable)中。
这样HBase的HRegionServer 就通过LSM-tree 实现了数据文件的生成。
lsm-tree 这种结构非常由利于数据的快速写入(理论上可以接近磁盘顺序写速度)。但读取过程可能不太友好(因为理论上读的时候可能需要同时从memtable和所有磁盘上的sstable 中查询数据,这样显然会对性能做成较大的影响 )
为了解决读取的问题,Lsm-tree 采取了以下主要的相关措施。
(1)、定期将磁盘上小的sstable 合并(通常叫做merge 或compaction 操作)成大的sstable,以减少sstable的数量,并且平时数据的更新删除操作并不会更新原有的数据文件,只会将更新和操作加到当前的数据文件末端,只有再sstable 合并的时候才会真正将重复的操作或更新去重、合并。
(2)对每个sstable 使用(bloom filter),以加速对数据在该sstable 的存在性进行判定,从而减少数据的总查询时间。

二、druid 应用 LSM-tree 架构
LSM-tree 架构显然比较适合那些数据插入操作远多于数据更新删除操作和读操作的场景,同时Druid 在一开始就是为时序数据场景设计的,而该场景正好符合LSM-tree 的特定,因此Druid 架构便顺理成章的吸取了其思想。
druid 的类LSM-tree 架构中的实时节点(Realtime Node),负责实时消费实时数据,与经典的LSM-tree 架构不同的是,Druid 部提供日志及实行原则,
第一步、
实时数据首相会被直接加载进实时系欸DNA内存中的对结构缓冲区(相当于 memtable),当条件满足时,缓存区里的数据会被冲写到磁盘上形成一个数据块(segment split),同时实时节点又会立即将新生成的数据块加载到内存的非堆区,因此无论时堆结构缓存区还是非堆区里的数据,都能够被查询节点(Broker Node)查询。实时节点数据块的生成示意图如

第二步
实时节点会周期性的将磁盘上同一个时间段内生成的所有数据块合并为一个大的数据块(Segment)。这个过程在实时节点中叫做 Segment merge 操作,也相当于LSM-tree 架构中的数据合并操作。合并好的Segment 会立即被实时节点上传到数据文件存储库(DeepStorage)中,
第三步
协调节点(Coordinator node) 会知道一个历史节点(Historical NOde) 去文件存储库,将新生成的segment 下载到本地磁盘中,当历史节点 成功加载到新的Segment 后,会通过分布式协调服务(coordinator)在集群中声明其从此刻开始负责该segment 的查询。接下来查询节点会转从该历史节点查询此segement 的数据。
第四步
对于一个查询来说,会分成两部分。查询节点会同时从实时节点(少量当前数据)与历史节点(大量历史数据)分别查询,然后做一个结果的整合,最后再返回给用户。druid 的这种架构安排一定程度上借鉴名利查询职责分离模式。
这是druid 不同于Hbase 等LSM -tree 系架构的显著特定。
三、kudu 应用LSM-tree 结构
kudu 写入流程:
第一步:
客户端连接TMaster 获取表的位置信息。然后找到对应 TServer
Kudu在Tablet中的所有rowset(memrowset,diskrowset)中进行查找,看是否存在与待插入数据相同主键的数据,如果存在就返回错误,否则继续。
第二部
写入操作先被提交到tablet的预写日志(WAL),让会数据会被添加到其中的一个tablet 的内存中,(插入会被添加到tablet的MemRowSet中; 更新和删除操作将被追加到MemRowSet中的原始行之后以生成redo记录的列表)
第三部
kudu 再memRowSet中写入一行新数据,在memRowset(1G或120 s),memRowset 将数据落盘,并生成一个 diskrowset用于持久化数据还生成一个memrowset继续解说新数据的请求。
注意:更新逻辑为分为两种情况。
(1)、当待更新数据可能位于memrowset 中,找到待更新数据所在的行,然后将更新操作记录在所在行中的一个mutation 链表中,与插入数据不同的位置上,在memrowset 将数据落盘时,kudu 会将更新合并到base data,并生成UnDO UNDO records用于查看历史版本的数据
(2)、当待更新数据位于disKRowSet 时
找到待更新数据所在的 DiskRowset,每个diskRowset都会在内存中设置一个DeltaMemstore,将更新操作记录在DeltaMemstore中,更新操作记录在DeltaMemStore中,在DeltaMemStore达到一定大小时,flush在磁盘,形成DeltaFile中
四、对比 hbase 和kudu LSM
HBase 的存储实现是LSM 的典型应用,kudu 堆LSM 做了一些优化。
第一部分
kudu 的memtable (在kudu 中叫memRowset) 还是同之前一样,只是
SSTable(在kudu 中叫 diskRowset)改成了列式存储。对于列式存储,读取一个一个记录需要分别读取每个字段,因此kudu精心设计了RowSet中的索引(针对并发访问等改进过的B树),加速这个过程。
第二部分
除了列式存储,kudu保证一个key只可能出现在一个RowSet中,并记录了每个RowSet中key的最大值和最小值,加速数据的范围查找。这也意味着,对于数据更新,不能再像之前一样直接插入memtable即可。需要找到对应的RowSet去更新,为了保持写吞吐,kudu并不直接更新RowSet,而是又新建一个
DeltaStore,专门记录数据的更新。所以,后台除了RowSet的compaction线程,还要对DeltaStore进行merge和apply。从权衡的角度考虑,kudu其实是牺牲了一点写效率,单记录查询效率,换取了批量查询效率。

本文介绍了LSM思想在大数据生态圈的应用,阐述了LSM-tree架构特点。详细分析了Hbase、Druid、Kudu应用LSM-tree架构的情况,包括数据写入、查询等流程。最后对比了Hbase和Kudu的LSM架构,指出Kudu做了一些优化,牺牲部分写效率换取批量查询效率。
574





