庖丁解LevelDB之版本控制

版本控制或元信息管理,是LevelDB中比较重要的内容。本文首先介绍其在整个LevelDB中不可替代的作用;之后从代码结构引出其实现方式;最后由几个主要的功能点入手详细介绍元信息管理是如何提供不可或缺的支撑的。

作用

通过之前的博客,我们已经了解到了LevelDB整个的工作过程以及从Memtable,Log到SST文件的存储方式。那么问题来了,LevelDB如何能够知道每一层有哪些SST文件;如何快速的定位某条数据所在的SST文件;重启后又是如何恢复到之前的状态的,等等这些关键的问题都需要依赖元信息管理模块。对其维护的信息及所起的作用简要概括如下:

  • 记录Compaction相关信息,使得Compaction过程能在需要的时候被触发;
  • 维护SST文件索引信息及层次信息,为整个LevelDB的读、写、Compaction提供数据结构支持;
  • 负责元信息数据的持久化,使得整个库可以从进程重启或机器宕机中恢复到正确的状态;
  • 记录LogNumber,Sequence,下一个SST文件编号等状态信息;
  • 以版本的方式维护元信息,使得Leveldb内部或外部用户可以以快照的方式使用文件和数据。

下面就将更详细的进行说明。

实现

LeveDB用Version表示一个版本的元信息,Version中主要包括一个FileMetaData指针的二维数组,分层记录了所有的SST文件信息。FileMetaData数据结构用来维护一个文件的元信息,包括文件大小,文件编号,最大最小值,引用计数等,其中引用计数记录了被不同的Version引用的个数,保证被引用中的文件不会被删除。除此之外,Version中还记录了触发Compaction相关的状态信息,这些信息会在读写请求或Compaction过程中被更新。通过庖丁解LevelDB之概览中对Compaction过程的描述可以知道在CompactMemTable和BackgroundCompaction过程中会导致新文件的产生和旧文件的删除。每当这个时候都会有一个新的对应的Version生成,并插入VersionSet链表头部。

VersionSet是一个Version构成的双向链表,这些Version按时间顺序先后产生,记录了当时的元信息,链表头指向当前最新的Version,同时维护了每个Version的引用计数,被引用中的Version不会被删除,其对应的SST文件也因此得以保留,通过这种方式,使得LevelDB可以在一个稳定的快照视图上访问文件。VersionSet中除了Version的双向链表外还会记录一些如LogNumber,Sequence,下一个SST文件编号的状态信息。

VersionSet Version 示意图

通过上面的描述可以看出,相邻Version之间的不同仅仅是一些文件被删除另一些文件被删除。也就是说将文件变动应用在旧的Version上可以得到新的Version,这也就是Version产生的方式。LevelDB用VersionEdit来表示这种相邻Version的差值。

VersionEidt

为了避免进程崩溃或机器宕机导致的数据丢失,LevelDB需要将元信息数据持久化到磁盘,承担这个任务的就是Manifest文件。可以看出每当有新的Version产生都需要更新Manifest,很自然的发现这个新增数据正好对应于VersionEdit内容,也就是说Manifest文件记录的是一组VersionEdit值,在Manifest中的一次增量内容称作一个Block,其内容如下:

Manifest Block := N * Item
Item := [kComparator] comparator
        or [kLogNumber] 64位log_number
        or [kPrevLogNumber] 64位pre_log_number
        or [kNextFileNumber] 64位next_file_number_
        or [kLastSequence] 64位last_sequence_
        or [kCompactPointer] 32位level + 变长的key
        or [kDeletedFile] 32位level + 64位文件号
        or [kNewFile] 32位level + 64位 文件号 + 64位文件长度 + smallest key + largest key

可以看出恢复元信息的过程也变成了依次应用VersionEdit的过程,这个过程中有大量的中间Version产生,但这些并不是我们所需要的。LevelDB引入VersionSet::Builder来避免这种中间变量,方法是先将所有的VersoinEdit内容整理到VersionBuilder中,然后一次应用产生最终的Version,这种实现上的优化如下图所示:

VersionSet::Builder

在这一节中,我们依次看到了LevelDB版本控制中比较重要的几个角色:Version、FileMetaData、VersionSet、VersionEdit、Manifest和Version::Builder。同时了解了他们各自的作用。接下来就一起从LevelDB主要的功能点中欣赏下他们的英姿。

功能点

版本控制中维护的各种元信息,为LevelDB的各个工作流程中提供了必不可少的支持:

1,Get

我们已经知道,LevelDB尝试获取某个Key的值时会依次尝试从Memtable,Immutable,SST文件中读取。一旦需要从SST文件中读取,就需要解决从大量文件中快速定位文件的问题。正是由于Version中记录了当前每个文件的最大最小值,使得这个问题变成比较Key值与文件的Key Range的过程。

我们已经知道,LevelDB的写操作会直接写入Memtable并通过异步的Compaction过程写入到不同层次的SST文件中,因此,上层文件拥有较新的数据,利用这个特征,LevelDB的Get接口会由上至下的依次从每一层中尝试查找,一旦查找成功,便可以忽略下层的相同Key的记录。

Level0层比较特殊,文件之间相互重叠无序,需要由新到旧的尝试从每个文件中查找。其他Level,由于SST文件本身有序排列,因此可以利用二分查找快速定位Key所在文件。找到Key值所在文件后,再用庖丁解LevelDB之数据存储中介绍的格式读取文件中内容。

2,Compaction触发时机

我们已经知道,LevelDB中会有后台线程来执行Compaction的操作,将上层文件与下层文件归并生成新的下层文件。Version中记录的各层的文件信息来帮助决定进行Compaction的时机:

  • 容量触发Compaction:每个Version在其生成的时候会初始化两个值compaction_level_、compaction_score_,记录了当前Version最需要进行Compaction的Level,以及其需要进行Compaction的紧迫程度,score大于1被认为是需要马上执行的。我们知道每次文件信息的改变都会生成新的Version,所以每个Version对应的这两个值初始化后不会再改变。level0层compaction_score_与文件数相关,其他level的则与当前层的文件总大小相关。这种区分的必要性也是显而易见的:每次Get操作都需要从level0层的每个文件中尝试查找,因此控制level0的文件数是很有必要的。同时Version中会记录每层上次Compaction结束后的最大Key值compact_pointer_,下一次触发自动Compaction会从这个Key开始。容量触发的优先级高于下面将要提到的Seek触发。
  • Seek触发Compaction:Version中会记录file_to_compact_和file_to_compact_level_,这两个值会在Get操作每次尝试从文件中查找时更新。LevelDB认为每次查找同样会消耗IO,这个消耗在达到一定数量可以抵消一次Compaction操作消耗的IO,所以对Seek较多的文件应该主动触发一次Compaction。但在引入布隆过滤器后,这种查找消耗的IO就会变得微不足道了,因此由Seek触发的Compaction其实也就变得没有必要了。
  • 手动Compaction:LevelDB提供了外部接口CompactRange,用户可以指定触发某个Key Range的Compaction,LevelDB默认手动Compaction的优先级高于两种自动触发。

3,构造Compaction:

达到触发条件进行Compaction操作时,会首先通过Version来构造所有本次Compaction所需要的信息,记录在Compaction对象中,包括发生Compaction的level,所有参与的level和level+1层的文件信息,level+2层的文件信息等。 下面针对自动触发Compaction的情况介绍,手动Compaction的过程大体类似,这个过程叫做PickCompaction。

  • 获得要Compaction的一个文件加入input_[0],容量触发时这个文件由compaction_level_加compact_pointer_确定,否则由file_to_compact_level_和file_to_compact_确定。对于level0,由于其文件相互重合,需要将所有与当前Compaction文件重合的文件全部加入input_[0]。
  • 获得所有与level[0]有Key Range重合的level+1层文件加入input_[1],可以看出所有input_[1]文件的Key Range可能大于level[0],为了减少LevelDB整体Compaction次数,LevelDB会在不增加input_[1]文件数的前提下尝试增加level[0]文件数来扩大level层文件的Key Range。
  • 获得所有与当前Key Range重合的level+2层文件加入input_[2],这里记录level+2层的文件信息是为了Compaction生成新的level+1层文件时,保证新文件不会与level+2中太多的文件有Key Range的重合,从而导致以后该文件的Compaction有太大的Merge开销,这个信息会在生成新文件的过程中不断检查。
  • 生成归并Iterator,接下来就是用上面收集的信息生成归并Iterator,之后遍历这个Iterator生成新的文件,Iterator相关的内容会在之后一篇博客详细介绍。

4,Version持久化:

Compaction过程会造成文件的增加和删除,这就需要生成新的Version,上面提到的Compaction对象包含本次Compaction所对应的VersionEdit,Compaction结束后这个VersionEdit会被用来构造新的VersionSet中的Version。同时为了数据安全,这个VersionEdit会被Append写入到Manifest中。在库重启时,会首先尝试从Manifest中恢复出当前的元信息状态,过程如下:

  • 依次读取Manifest文件中的每一个Block, 将从文件中读出的Record反序列化为VersionEdit;
  • 将每一个的VersionEdit Apply到VersionSet::Builder中,之后从VersionSet::Builder的信息中生成Version;
  • 计算compaction_level_、compaction_score_;
  • 将新生成的Version挂到VersionSet中,并初始化VersionSet的manifest_file_number_, next_file_number_,last_sequence_,log_number_,prev_log_number_ 信息;

总结

版本控制或元信息管理,在LevelDB的各个流程中穿针引线,保证了整个数据库的正确稳定,不可或缺。接下来的一篇博客将着重介绍,在LevelDB中出镜率极高又十分优雅的Iterator。

参考

Source Code:https://github.com/google/leveldb

庖丁解LevelDB之概览: http://catkang.github.io/2017/01/07/leveldb-summary.html

庖丁解LevelDB之数据管理: http://catkang.github.io/2017/01/07/leveldb-summary.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • LevelDB是Google传奇工程师Jeff Dean和Sanjay Ghemawat开源的KV存储引擎,无论从...
    CatKang阅读 4,765评论 5 25
  • 前面写了两篇文章介绍 LevelDB 的整体架构和接口使用。这篇文章,我们从代码的角度看看 LevelDB 的设计...
    linjinhe阅读 5,399评论 0 1
  • 9.1 基本原理 版本信息有什么用?先来简要说明三个类的具体用途: Version:代表了某一时刻的数据库版本信息...
    随安居士阅读 2,456评论 0 3
  • 通过之前对LevelDB的整体流程,数据存储以及元信息管理的介绍,我们已经基本完整的了解了LevelDB。接下来两...
    CatKang阅读 3,622评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139