### 前言
十多年前,谷歌发布了大名鼎鼎的"三驾马车"的论文,分别是GFS(2003年),MapReduce(2004年),BigTable(2006年),为开源界在大数据领域带来了无数的灵感,其中在 “BigTable” 的论文中很多很酷的方面之一就是它所使用的文件组织方式,这个方法更一般的名字叫 Log Structured-Merge Tree。在面对亿级别之上的海量数据的存储和检索的场景下,我们选择的数据库通常都是各种强力的NoSQL,比如Hbase,Cassandra,Leveldb,RocksDB等等,这其中前两者是Apache下面的顶级开源项目数据库,后两者分别是Google和Facebook开源的数据库存储引擎。而这些强大的NoSQL数据库都有一个共性,就是其底层使用的数据结构,都是仿照“BigTable”中的文件组织方式来实现的,也就是我们今天要介绍的LSM-Tree。
### 什么是LSM-Tree
LSM-Tree全称是Log Structured Merge Tree,是一种分层,有序,面向磁盘的数据结构,其核心思想是充分了利用了,磁盘批量的顺序写要远比随机写性能高出很多,如下图示:
围绕这一原理进行设计和优化,以此让写性能达到最优,正如我们普通的Log的写入方式,这种结构的写入,全部都是以Append的模式追加,不存在删除和修改。当然有得就有舍,这种结构虽然大大提升了数据的写入能力,却是以牺牲部分读取性能为代价,故此这种结构通常适合于写多读少的场景。故LSM被设计来提供比传统的B+树或者ISAM更好的写操作吞吐量,通过消去随机的本地更新操作来达到这个目标。这里面最典型的例子就属于Kakfa了,把磁盘顺序写发挥到了极致,故而在大数据领域成为了互联网公司标配的分布式消息中间件组件。
虽然这种结构的写非常简单高效,但其缺点是对读取特别是随机读很不友好,这也是为什么日志通常用在下面的两种简单的场景:
(1) 数据是被整体访问的,大多数数据库的WAL(write ahead log)也称预写log,包括mysql的Binlog等
(2) 数据是通过文件的偏移量offset访问的,比如Kafka。
想要支持更复杂和高效的读取,比如按key查询和按range查询,就得需要做一步的设计,这也是LSM-Tree结构,除了利用磁盘顺序写之外,还划分了内存+磁盘多层的合并结构的原因,正是基于这种结构再加上不同的优化实现,才造就了在这之上的各种独具特点的NoSQL数据库,如Hbase,Cassandra,Leveldb,RocksDB,MongoDB,TiDB等。
### SSTable 和 LSM-Tree
提到LSM-Tree这种结构,就得提一下LevelDB这个存储引擎,我们知道Bigtable是谷歌开源的一篇论文,很难接触到它的源代码实现。如果说Bigtable是分布式闭源的一个高性能的KV系统,那么LevelDB就是这个KV系统开源的单机版实现,最为重要的是LevelDB是由Bigtable的原作者 Jeff Dean 和 Sanjay Ghemawat 共同完成,可以说高度复刻了Bigtable 论文中对于其实现的描述。
在LSM-Tree里面,核心的数据结构就是SSTable,全称是Sorted String Table,SSTable的概念其实也是来自于 Google 的 Bigtable 论文,论文中对 SSTable 的描述如下:
```
An SSTable provides a persistent, ordered immutable map from keys to values, where both keys and values are arbitrary byte strings. Operations are provided to look up the value associated with a specified key, and to iterate over all key/value pairs in a specified key range. Internally, each SSTable contains a sequence of blocks (typically each block is 64KB in size, but this is configurable). A block index (stored at the end of the SSTable) is used to locate blocks; the index is loaded into memory when the SSTable is opened. A lookup can be performed with a single disk seek: we first find the appropriate block by performing a binary search in the in-memory index, and then reading the appropriate block from disk. Optionally, an SSTable can be completely mapped into memory, which allows us to perform lookups and scans without touching disk.
```
如上所述,SSTable是一种拥有持久化,有序且不可变的的键值存储结构,它的key和value都是任意的字节数组,并且了提供了按指定key查找和指定范围的key区间迭代遍历的功能。SSTable内部包含了一系列可配置大小的Block块,典型的大小是64KB,关于这些Block块的index存储在SSTable的尾部,用于帮助快速查找特定的Block。当一个SSTable被打开的时候,index会被加载到内存,然后根据key在内存index里面进行一个二分查找,查到该key对应的磁盘的offset之后,然后去磁盘把响应的块数据读取出来。当然如果内存足够大的话,可以直接把SSTable直接通过MMap的技术映射到内存中,从而提供更快的查找。
在LSM-Tree里,SSTable有一份在内存里面,其他的多级在磁盘上,如下图是一份完整的LSM-Tree图示:
我们总结下在在LSM-Tree里面如何写数据的?
1,当收到一个写请求时,会先把该条数据记录在WAL Log里面,用作故障恢复。
2,当写完WAL Log后,会把该条数据写入内存的SSTable里面(删除是墓碑标记,更新是新记录一条的数据),也称Memtable。注意为了维持有序性在内存里面可以采用红黑树或者跳跃表相关的数据结构。
3,当Memtable超过一定的大小后,会在内存里面冻结,变成不可变的Memtable,同时为了不阻塞写操作需要新生成一个Memtable继续提供服务。
4,把内存里面不可变的Memtable给dump到到硬盘上的SSTable层中,此步骤也称为Minor Compaction,这里需要注意在L0层的SSTable是没有进行合并的,所以这里的key range在多个SSTable中可能会出现重叠,在层数大于0层之后的SSTable,不存在重叠key。
5,当每层的磁盘上的SSTable的体积超过一定的大小或者个数,也会周期的进行合并。此步骤也称为Major Compaction,这个阶段会真正 的清除掉被标记删除掉的数据以及多版本数据的合并,避免浪费空间,注意由于SSTable都是有序的,我们可以直接采用merge sort进行高效合并。
接着我们总结下在LSM-Tree里面如何读数据的?
1,当收到一个读请求的时候,会直接先在内存里面查询,如果查询到就返回。
2,如果没有查询到就会依次下沉,知道把所有的Level层查询一遍得到最终结果。
思考查询步骤,我们会发现如果SSTable的分层越多,那么最坏的情况下要把所有的分层扫描一遍,对于这种情况肯定是需要优化的,如何优化?在 Bigtable 论文中提出了几种方式:
1,压缩
SSTable 是可以启用压缩功能的,并且这种压缩不是将整个 SSTable 一起压缩,而是根据 locality 将数据分组,每个组分别压缩,这样的好处当读取数据的时候,我们不需要解压缩整个文件而是解压缩部分 Group 就可以读取。
2,缓存
因为SSTable在写入磁盘后,除了Compaction之外,是不会变化的,所以我可以将Scan的Block进行缓存,从而提高检索的效率
3,索引,Bloom filters
正常情况下,一个读操作是需要读取所有的 SSTable 将结果合并后返回的,但是对于某些 key 而言,有些 SSTable 是根本不包含对应数据的,因此,我们可以对每一个 SSTable 添加 Bloom Filter,因为布隆过滤器在判断一个SSTable不存在某个key的时候,那么就一定不会存在,利用这个特性可以减少不必要的磁盘扫描。
4,合并
这个在前面的写入流程中已经介绍过,通过定期合并瘦身, 可以有效的清除无效数据,缩短读取路径,提高磁盘利用空间。但Compaction操作是非常消耗CPU和磁盘IO的,尤其是在业务高峰期,如果发生了Major Compaction,则会降低整个系统的吞吐量,这也是一些NoSQL数据库,比如Hbase里面常常会禁用Major Compaction,并在凌晨业务低峰期进行合并的原因。
最后有的同学可能会问道,为什么LSM不直接顺序写入磁盘,而是需要在内存中缓冲一下? 这个问题其实很容易解答,单条写的性能肯定没有批量写来的块,这个原理其实在Kafka里面也是一样的,虽然kafka给我们的感觉是写入后就落地,但其实并不是,本身是 可以根据条数或者时间比如200ms刷入磁盘一次,这样能大大提升写入效率。此外在LSM中,在磁盘缓冲的另一个好处是,针对新增的数据,可以直接查询返回,能够避免一定的IO操作。
### B+Tree VS LSM-Tree
传统关系型数据采用的底层数据结构是B+树,那么同样是面向磁盘存储的数据结构LSM-Tree相比B+树有什么异同之处呢?
LSM-Tree的设计思路是,将数据拆分为几百M大小的Segments,并是顺序写入。
B+Tree则是将数据拆分为固定大小的Block或Page, 一般是4KB大小,和磁盘一个扇区的大小对应,Page是读写的最小单位。
在数据的更新和删除方面,B+Tree可以做到原地更新和删除,这种方式对数据库事务支持更加友好,因为一个key只会出现一个Page页里面,但由于LSM-Tree只能追加写,并且在L0层key的rang会重叠,所以对事务支持较弱,只能在Segment Compaction的时候进行真正地更新和删除。
因此LSM-Tree的优点是支持高吞吐的写(可认为是O(1)),这个特点在分布式系统上更为看重,当然针对读取普通的LSM-Tree结构,读取是O(N)的复杂度,在使用索引或者缓存优化后的也可以达到O(logN)的复杂度。
而B+tree的优点是支持高效的读(稳定的OlogN),但是在大规模的写请求下(复杂度O(LogN)),效率会变得比较低,因为随着insert的操作,为了维护B+树结构,节点会不断的分裂和合并。操作磁盘的随机读写概率会变大,故导致性能降低。
还有一点需要提到的是基于LSM-Tree分层存储能够做到写的高吞吐,带来的副作用是整个系统必须频繁的进行compaction,写入量越大,Compaction的过程越频繁。而compaction是一个compare & merge的过程,非常消耗CPU和存储IO,在高吞吐的写入情形下,大量的compaction操作占用大量系统资源,必然带来整个系统性能断崖式下跌,对应用系统产生巨大影响,当然我们可以禁用自动Major Compaction,在每天系统低峰期定期触发合并,来避免这个问题。
阿里为了优化这个问题,在X-DB引入了异构硬件设备FPGA来代替CPU完成compaction操作,使系统整体性能维持在高水位并避免抖动,是存储引擎得以服务业务苛刻要求的关键。
### 总结
本文主要介绍了LSM-Tree的相关内容,简单的说,其牺牲了部分读取的性能,通过批量顺序写来换取了高吞吐的写性能,这种特性在大数据领域得到充分了体现,最直接的例子就各种NoSQL在大数据领域的应用,学习和了解LSM-Tree的结构将有助于我们更加深入的去理解相关NoSQL数据库的实现原理,掌握隐藏在这些框架下面的核心知识。
- 大小: 11.1 KB
- 大小: 7.9 KB
- 大小: 66.9 KB
分享到:
相关推荐
LSM树(Log-Structured Merge Tree)是一种常用于大规模数据存储和检索的索引结构,尤其是在分布式数据库系统、键值存储系统以及日志...通过阅读"LSM-Tree.pdf"和"LSM.txt",可以深入理解LSM树的实现细节和优化技巧。
leveldb原理与核心算法介绍,重点介绍lsm-tree运行原理与leveldb的compaction过程,有助于深入理解leveldb的运行机制和算法过程,对leveldb的使用和调优有非常大的帮助
LSM-Tree(Log-Structured ...深入研究`shifterdb`的源码,我们可以学习到如何在实际项目中应用LSM-Tree和ACID事务,同时了解Go语言在数据库系统开发中的应用技巧。这对于我们提升数据库系统设计和实现的理解大有裨益。
总之,LSM Tree实验涉及了数据结构设计、内存管理、磁盘I/O优化以及C++编程技巧等多个方面,通过实验不仅理解了LSM Tree的工作原理,还解决了实际实现中的各种问题,提高了对数据结构和编程语言的深入理解。
通过分析和理解这段代码,可以深入学习LSM算法的工作原理和优化策略,并为自己的项目提供参考。 总结来说,LSM算法是一种关键的数据存储技术,尤其适用于大数据环境。帝王蝶优化算法则尝试利用生物启发式方法改进其...
在深入理解Badger之前,让我们先了解键值存储的基本概念。键值存储是一种数据存储模型,其中每个条目由一个唯一的键和与之关联的值组成。这种类型的数据库通常非常适合快速的读写操作,因为它们能够直接通过键来定位...
1. **LSM-Tree**:了解LSM-Tree的数据结构,它是RocksDB的基础,由内存中的Memtable和磁盘上的SSTable组成,以及其写优化的特点。 2. **压缩**:RocksDB支持多种数据压缩算法,可以减少存储空间,理解不同压缩方式...
1. **LSM-Tree**:理解其工作原理,包括写入流程、内存中的Memtable、磁盘上的SSTable以及合并过程。 2. **压缩算法**:如Zlib或Snappy,了解如何在leveldb中应用这些算法来提高存储效率。 3. **多版本并发控制**:...
通过“lsm.txt”和“www.pudn.com.txt”这两个文件,你可以深入理解LSM算法的实现细节,可能包括具体的编程实现、性能优化策略等。不过,具体的内容需要打开文件查看才能得知。如果你正在学习LSM算法,这两个文件将...
- 内存管理:深入理解动态内存分配(new/delete)和内存池的概念,以及如何提高内存分配的效率和减少碎片。 - 对象池设计模式:了解如何通过对象池来避免频繁的内存申请和释放操作。 3. **tinyhttpd**: - 网络...
**RocksDB 深度解析** RocksDB 是 Facebook 开源的一款高性能、可嵌入式的键值存储系统,主要用于处理大规模数据...开发者可以通过深入理解和定制化配置,充分利用其特性,为各种应用场景提供高效可靠的键值存储服务。
在 LevelDB 1.18 版本中,我们能够深入理解其内部实现机制,从而获得关于数据库引擎设计的宝贵知识。 1. **数据结构与文件组织** LevelDB 使用 SSTable(Sorted String Table)作为其基本的数据存储结构。SSTable ...
4. **硬件依赖性**:例如 PingCAP 的 TiDB 使用了特定的硬件配置和技术手段来弥补 LSM-TREE 存储引擎的不足,这可能对硬件提出了更高要求。 5. **社区支持与文档质量**:良好的社区支持和详尽的文档对于后期维护和...
它允许用户直观地查看、操作和管理LevelDB数据库,而无需深入理解底层数据结构和API。这款应用已经过编译,用户可以直接下载并使用,极大地简化了对LevelDB数据库的日常维护工作。 **Electron框架** Electron是由...
2. **索引机制**:了解 KitDB 的索引实现方式,如 B-Tree、Hash 索引或LSM-Tree(Log-Structured Merge Tree),这对于理解数据库的查询效率至关重要。 3. **并发控制**:在多线程环境中,数据库需要确保数据的一致...
10. **源码学习价值**:由于Leveldb的源码清晰、结构紧凑,是学习数据库实现、数据结构和算法的优秀实例,对于深入理解数据库工作原理有很大帮助。 总的来说,Leveldb因其高效、可靠和轻量级的特性,在许多实际应用...