LevelDB是google开源的key-value,单机,持久化存储引擎:
- 写操作:写Log的顺序磁盘写和内存中对memtable的操作;
- 读操作:设计一系列策略保证读的效率;
整体的架构如下:
在写入操作的时候,会先把要写入的操作顺序地写到日志文件中。这样,就算是在memtable的操作中失败了,重新启动的时候也是会从日志文件中还原,这样可靠性就得到了保障。又因为日志文件时顺序的IO,这样也提高了写的效率。日志文件的格式如下:
为了在重启db后可以恢复到退出前的状态,需要把db的状态保存在mainifest文件中:只在mainifest开始保存完整的状态信息,接下来只保存每次compact产生的操作(VersionEdit)。重启db时:获得开头的起始状态,并且依次将VersionEdit重新执行。
在日志文件中,文件开始部分用来存放一些信息,在读取的时候跳过。日志文件是按照块来处理的:一个块中会有一些Record,每个Record有四种类型:头、中、尾、全,这样在顺序读取的时候就能根据Record中的Type字段来获取完整的记录。
在完成Log文件的写入之后就需要来写Memtable,采用跳表的方式组织:
LevelDB中的跳表最多有12层,而且是只能插入。为了保证无锁操作的安全性,插入的Record的Key是不能重复的。所以,Record中的Key并非是用户传入的Key,而是结合SequenceNumber和KeyType生成的内部Key。
在Memtable足够大的时候,就会将其保存到磁盘上,也就是SSTable中,同时生成新的Memtable来接受写入请求。SSTable的结构如下:
在data_block中的每一个entry就是一条记录,由于使用前缀压缩的方法,所以需要记录公共前缀的长度。采用前缀压缩的坏处:在根据下标取得其中一条记录时需要从头开始遍历,这样的效率比较低,所以在Data_block中将所有的记录分成几个区域,在restarts中保存了分割的方法。
在每个SSTable中用footer来保存数据块等结构开始的位置。
在Index_block中保存了每个Data_block的最大的Key(大概可以这么说)以及偏移量,这样通过二分能加快查找速度。
LevelDB可能将Memtable结构Dump成多个SSTable,这些文件中的Key的范围可能有重叠部分。所有的SSTable文件可以分为7个Level,刚Dump成的文件属于Level-0,也只有这个Level中的文件的Key存在重叠。
LevelDB根据可以查找的过程大致如下:
LevelDB中包括两种Compaction操作:
- minor:把Memtable中的数据保存到SSTable中;
- major:合并不同层级的SSTable文件;
当一个层级中的SSTable文件超过一定数目(应该是随着Level数按指数增长)就会从该层的SSTable文件中选择一个文件和高一层中的文件进行合并操作。对应Level-0和其他层级有不同的操作:
- Level-0:因为可能有其他文件和被选中的文件中的Key范围有重叠,这时候要将所有有重叠的文件都进行合并;
- Level-N:因为该层的任意两个文件不存在重叠,所以只合并选中的文件;
在底层选择要合并文件时采用轮流的方式,而在高层选择时,为了保证合并之后各层的文件仍然是有序的,所以要选出所有更高层中与该文件的范围有重叠的文件进行合并。现在的问题是什么时候执行compaction操作?
- 对于Level-0,因为是无序的,内容太多会严重地影响查询的速度,所以如果文件总大小超过一定限制的时候就会触发compact;
- 对于Level-N,因为这些是有序的可以进行二分查找,所以影响查询速度的重点是文件数目;
上面的操作貌似大部分都是在磁盘上的,LevelDB会对文件“元信息”和数据信息做缓存,在查找的时候会先尝试从内存中获取。
http://blog.sina.com.cn/s/blog_aed82f6f0102wn76.html