前言
2006年的OSDI有两篇Google的论文,分别是BigTable和Chubby。Chubby是一个分布式锁服务,基于Paxos算法;BigTable是一个用于管理结构化数据的分布式存储系统,构建在GFS、Chubby、SSTable等google技术之上。相当多的google应用使用了BigTable,比如Google Earth和Google Analytics,因此它和GFS、MapReduce并称为谷歌技术"三宝"。
Google发布的BigTable这篇论文的一个很有价值的方面是它使用的文件组织,通常被称为日志结构的合并树(Log Structured-Merge Tree)。LSM现在作为主要的文件组织策略在许多产品中使用,比如HBase, Cassandra, LevelDB, SQLite,甚至MongoDB 3.0都在收购Wired Tiger后附带了可选的LSM引擎。LSM树的有趣之处在于它脱离了统治该领域几十年的二叉树风格的文件组织,本篇文章主要介绍下其大概的原理实现。
LSM-tree
日志结构的合并树(LSM-tree)是一种分层的、有序的、基于硬盘的数据结构,它的核心思路其实非常简单,首先写入数据到内存中,不需要每次有数据更新就必须将数据写入到磁盘中,等到积累到一定阈值之后,再使用归并排序的方式将内存中的数据合并追加到磁盘队尾(因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。
LSM-tree为什么这么设计呢,这么设计有什么好处呢?
我们知道一般情况下磁盘在随机操作时速度很慢,但在顺序访问时速度很快,其实无论是磁盘还是主存(虽然程度较轻),随机和顺序这两种访问方式之间都存在一条鸿沟,如下图引入ACM的测试报告,顺序磁盘访问要比随机访问快很多。所以磁盘有如下技术特性:对磁盘来说,能够最大化的发挥磁盘技术特性的使用方式是:一次性的读取或写入固定大小的一块数据,并尽可能的减少随机寻道这个操作的次数。LSM-tree就是充分地利用了这种思想而实现的,批次顺序写入大大提高了写性能,因此LSM被设计来提供比传统的B+树或者ISAM更好的写操作吞吐量,通过消去随机的本地更新操作来达到这个目标。
然而这种结构虽然大大提升了数据的写入能力,却是以牺牲部分读取性能为代价,通常适合于写多读少的场景。
与传统存储引擎对比
首先看下哈希、B树、LSM树的特点:
- 哈希存储引擎 是哈希表的持久化实现,支持增、删、改以及随机读取操作,但不支持顺序扫描,对应的存储系统为key-value存储系统。对于key-value的插入以及查询,哈希表的复杂度都是O(1),明显比树的操作O(n)快,如果不需要有序的遍历数据,哈希表就是your Mr.Right
- B树存储引擎是B树(的持久化实现,不仅支持单条记录的增、删、读、改操作,还支持顺序扫描(B+树的叶子节点之间的指针),对应的存储系统就是关系数据库(Mysql等)。
- LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎一样,同样支持增、删、读、改、顺序扫描操作。而且通过批量存储技术规避磁盘随机写入问题。当然凡事有利有弊,LSM树和B+树相比,LSM树牺牲了部分读性能,用来大幅提高写性能。
哈希存储引擎我们不做过多的分析,主要对比下LSM树和B树的特性,LSM和Btree差异就是要在读性能和写性能进行舍和求,在牺牲读性能的同时,寻找其他方案来弥补。
1、LSM具有批量特性,存储延迟。当写读比例很大的时候(写比读多),LSM树相比于B树有更好的性能。因为随着insert操作,为了维护B树结构,节点分裂。读磁盘的随机读写概率会变大,性能会逐渐减弱。多次单页随机写,变成一次多页随机写,复用了磁盘寻道时间,极大提升效率。
2、B树的写入过程:B树的写入过程是一次原位写入的过程,主要分为两个部分,首先是查找到对应的块的位置,然后将新数据写入到刚才查找到的数据块中,然后再查找到块所对应的磁盘物理位置,将数据写入去。当然,在内存比较充足的时候,因为B树的一部分可以被缓存在内存中,所以查找块的过程有一定概率可以在内存内完成,不过为了表述清晰,我们就假定内存很小,只够存一个B树块大小的数据吧。可以看到,在上面的模式中,需要两次随机寻道(一次查找,一次原位写),才能够完成一次数据的写入,代价还是很高的。
3、LSM Tree放弃磁盘读性能来换取写的顺序性,但是大部分系统的使用场景是读多写少,读应该是大部分系统最应该保证的特性,为什么会用用读性能换取写性能呢?
a、内存的速度远超磁盘,1000倍以上。而读取的性能提升,主要还是依靠内存命中率而非磁盘读的次数
b、写入不占用磁盘的io,读取就能获取更长时间的磁盘io使用权,从而也可以提升读取效率。例如LevelDb的SSTable虽然降低了了读的性能,但如果数据的读取命中率有保障的前提下,因为读取能够获得更多的磁盘io机会,因此读取性能基本没有降低,甚至还会有提升。而写入的性能则会获得较大幅度的提升,基本上是5~10倍左右。