3. MemTable和Immutable Memtable
1. LevelDb组成
LevelDb作为存储系统,数据记录的存储介质包括内存以及磁盘文件,当LevelDb运行了一段时间,从静态角度看,LevelDb的组成如下图所示:
从图中可以看出,构成LevelDb静态结构的包括六个主要部分:
- 内存的数据结构:MemTable和Immutable MemTable
- 磁盘4种主要文件:Current文件,Manifest文件,log文件,SSTable文件
当然,LevelDb除了这六个主要部分还有一些辅助的文件,但是以上六个文件和数据结构是LevelDb的主体构成元素。这六个部分的配合关系如下,当往系统中插入一条键值对记录时:
(1)LevelDb会先往log文件里写入,Log文件在系统中的作用主要是用于系统崩溃恢复而不丢失数据,一个log文件对应一个Memtable
(2)log文件写入成功后将记录插进Memtable中,Memtable的底层数据结构是一个SkipList
(3)Memtable插入的数据占用内存到了一个界限后,LevleDb会生成新的Log文件和Memtable,原先的Memtable就成为Immutable Memtable,Immutable Memtable只接受读操作,不再接受写操作
(4)LevelDb后台调度会将Immutable Memtable的数据导出到磁盘,形成一个新的SSTable文件
(5)SSTable中的某个文件属于特定层级,而且其存储的记录是key有序的,Manifest文件记载了SSTable各个文件的管理信息,比如属于哪个Level,文件名,最小key和最大key各自是多少,manifest会记载所有SSTable文件的这些信息
(6)Current文件的内容只有一个信息,就是记载当前的manifest文件名。因为在LevleDb的运行过程中,随着Compaction的进行,Manifest也会跟着反映这种变化,此时往往会新生成Manifest文件来记载这种变化,而Current则用来指出哪个Manifest文件才是我们关心的那个Manifest文件
2. Log文件
上节内容讲到log文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据。因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及Dump到磁盘的SSTable文件,LevelDB也可以根据log文件恢复内存的Memtable数据结构内容,不会造成系统丢失数据,在这点上LevelDb和Bigtable是一致的。下面看看log文件的具体物理和逻辑布局是怎样的:
(1)物理布局
LevelDb对于一个log文件,会把它切割成以32K为单位的物理Block,每次读取的单位以一个Block作为基本读取单位,所以从物理布局来讲,一个log文件就是由连续的32K大小Block构成的,一个Block可能只包含一条记录,也可能包含多条记录。
(2)逻辑布局
在应用的视野里是看不到这些Block的,应用看到的是一系列的Key:Value对,在LevelDb内部,会将一个Key:Value对看做一条记录的数据,另外在这个数据前增加一个记录头,用来记载一些管理信息,以方便内部处理。
记录头包含三个字段:
- ChechSum:该字段是对“类型”和“数据”字段的校验码,大小为4B,为了避免处理不完整或者是被破坏的数据,当LevelDb读取记录数据时候会对数据进行校验,如果发现和存储的CheckSum相同,说明数据完整无破坏,可以继续后续流程
- Length:该字段记载了数据的大小
- payload:该字段则是上面讲的Key:Value数值对
- Type:该字段则指出了每条记录的逻辑结构和log文件物理分块结构之间的关系,具体而言,主要有以下四种类型:FULL/FIRST/MIDDLE/LAST
如果记录类型是FULL,代表了当前记录内容完整地存储在一个物理Block里,没有被不同的物理Block切割开;如果记录被相邻的物理Block切割开,则类型会是其他三种类型中的一种。假设目前存在三条记录,Record A,Record B和Record C,其中Record A大小为10K,Record B 大小为80K,Record C大小为12K,那么其在log文件中的逻辑布局会如下图所示:
- Record A因为大小为10K < 32K,能够放在一个物理Block中,所以其类型为FULL
- Record B 大小为80K,而Block 1因为放入了Record A,所以还剩下22K,不足以放下Record B,所以在Block 1的剩余部分放入Record B的开头一部分,类型标识为FIRST,代表了是一个记录的起始部分;Record B还有58K没有存储,这些只能依次放在后续的物理Block里面,因为Block 2大小只有32K,仍然放不下Record B的剩余部分,所以Block 2全部用来放Record B,且标识类型为MIDDLE,意思是这是Record B中间一段数据;Record B剩下的部分可以完全放在Block 3中,类型标识为LAST,代表了这是Record B的末尾数据
- Record C因为大小为12K,Block 3剩下的空间足以全部放下它,所以其类型标识为FULL