lsm树(log-Structured-Merge-Tree 日志结构合并树)被用作HBase、LevelDB、RocksDB的底层数据结构。是Google的BigTable论文中提到的一种文件组织数据结构。现在比较火的newsql数据库像tidb就是基于rocksdb。
本篇记录一下学习lsm的一些笔记,主要包含lsm树在存储和索引上的一些特性,与常见索引结构的比较,不包含并发控,事务处理和错误处理。
1、考虑通过两个bash函数实现一个简单的键值数据库mydb,
#!/bin/bash
db_set () {
echo "$1,$2" >> database
}
db_get () {
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}
2、db_set会将key和value存储在数据库中,db_get会将key对应的值返回。
[root@VM-0-11-centos tmp]# db_set key001 value0001
[root@VM-0-11-centos tmp]# db_set key002 value0002
[root@VM-0-11-centos tmp]# db_set key999 value999
[root@VM-0-11-centos tmp]#
[root@VM-0-11-centos tmp]# db_get key999
value999
[root@VM-0-11-centos tmp]#
3、底层的存储很简单为一个文本文件,每行包含一条逗号分隔的键值对。
dbset在一些场景拥有非常好的性能,许多数据库内部的 日志(log) 就是 仅追加(append-only) 的方式写入。mydb可以简单理解成:日志结构(log-structured) 的存储引擎类型。
[root@VM-0-11-centos tmp]# cat database
key001,value0001
key002,value0002
key999,value999
4、这个数据库有什么问题,当数据增长到一定数量,db_get查询函数的性能会非常糟糕,因为需要从头到尾扫描整个文件,也就是时间复杂度是0(N)。
为了高效查找,我们需要引入一种数据结构索引(index)。索引是一种额外的结构帮助我们更快的检索到我们需要的记录。但是维护索引需要产生额外的开销。
如果要为上面的键值数据库构建索引,哈希索引是一个明显不错的选择。在大多数编程语言中都可以找到散列映射(hash map) 如Java中的HashMap。所以我们现在用内存中的数据结构哈希映射来为mydb构建索引。
索引策略是:保留一个内存中的哈希结构,其中key是mydb的key,value是记录在database文件中的偏移位置offset。
还是以mydb为例子,当对同一个文件一直写入会出现最终用完磁盘空间,我们可以把日志文件分成特定大小的段(Segment)。
现在每个段文件都会拥有自己哈希索引,将键映射到文件偏移量。现在db_get操作需要先查找最近段的值,如果没有,检查第二新的段,以此类推。
到现在,mydb的日志文件段是一系列的键值对,这些键值对按照写入的顺序存储,日志中后面的值优先于前面的值。文件中的键值对本身没有顺序。
如果对要求日志段的键值对是有顺序的,就得到了lsm树主要的概念 排序字符串表(Sorted String Table)
,SSTable还要求每个键在同一个段文件只出现一次。
先查内存表,再查最近的磁盘文件段,以此类推。
基于这种合并和压缩排序文件原理的存储引擎通常被称为LSM存储引擎。其实就是memtable + SSTable + 日志合并操作得到的一个逻辑视图。
补充概念
B树 | Lsm树 |
---|---|
B树索引需要两次写入,预写日志和树页面本身 | - |
由于采取就地更新策略,写入操作可能涉及多个页面更新 | 较低的写放大,顺序写入,SSTable更紧凑 |
页面被分割或者拆分时候,会有空间浪费 | 压缩比b树更好,更多的小文件,较低的存储开销 |
读写行为相对有可预测性 | 压缩过程会干扰读写操作, |
由于磁盘io总量固定,随着文件越来越大,压缩操作可能会占据越来越多的磁盘io。如果写入吞吐很高,写入会跟不上压缩速度,需要监控。 | |
每个键在索引上只有一个,对于事务支持比较合适 | 由于一个键在多个文件存在,所以在行键加锁实现事务比较困难 |
1、设计数据密集型应用
2、大数据经典论文解读
笔者水平有限,不当之处大家拍砖
robben.hu