leveldb源码剖析--TableBuilder生成磁盘sstable

SSTable
table由按照rowkey排序后划分出的子片tablet组成,所以tablet是逻辑概念,tablet要持久化到GFS文件,每个文件称为SSTable.

TableBuilder
将数据写入磁盘生成sstable的工作由TableBuilder类完成。顾名思义,TableBuilder负责中封装了sstable的生成格式,它对用户的接口主要是

void Add(const Slice& key, const Slice& value);

函数,从函数的形式我们也可以看到,就是将键值对逐次加入到sstable中,而至于sstable中的其他管理块,比如index block,meta data block等内容则在调用Add的过程中,在TableBuilder类内部完成构建。本文主要是研究TableBuilder的具体实现,下图是TableBuilder的UML类图

image.png

可以看到TableBuilder中只有一个数据成员:rep_。

正如前面所说,TableBuilder是用于构建sstable的,而sstable里面还包含了各种block。比如data block,index block,metadata block等,不同的block可能有不同的存储格式,以及需要存储不同的信息。我们肯定不想让sstable管理所有类型的block的生成细节,那会使得TableBuilder过于臃肿,可选的解决方案是再向下封装一层Block,让Block类具体负责各种Block的生成细节。这里的rep_就是用于这个目的。
从下面的Rep的类图可以看出来


image.png

可以看到Rep中不仅接管了各种Block的生成细节,而且还会记录生成Block需要的一些统计信息。因此我们可以认为,TableBuilder只不过是对Block的一层薄薄的封装。,真正做事情的是Rep。而BuilderTable中的Add函数本质上不过是对Rep中的BlockBuilder或者FilterBlockBuilder的Add函数的调用。比如向datablock中添加数据,调用路径应该是这样的:

TableBuilder.Add -> rep_->data_block.Add

而data_block.Add会将数据写入到它的内存缓冲区中,当缓冲区的数据量达到某个阀值时,再将这个data block Flush到sstable(rep_->file)中,形成一个新的data block。当然,同时也会更新其他的block,对其他管理类的block的更新由TableBuilder协调完成,这也是TableBuilder的核心工作。

下面我们从TableBuilder.Add函数开始,一探其中奥秘。

TableBuilder.Add函数
正如前面所说,TableBuilder函数是对用户的写入接口,用户不能直接调用data Block的Add函数。
TableBuilder.Add函数的功能:向当前的data block写入一个key-value,同时更新index block,metadata block等块的内容。可以用下图直观第表示:


image.png

下面我们看一下函数源码:

void TableBuilder::Add(const Slice& key, const Slice& value) {
Rep* r = rep_;
assert(!r->closed);
if (!ok()) return;
if (r->num_entries > 0) {
assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0);
}

这几行代码主要是确定当前的sstable是否有效,因为可能当前的sstable已经被关闭或者丢弃了,以及确定当前sstable中的最大key(last_key)比这个新加入的key小。

if (r->pending_index_entry) {
assert(r->data_block.empty());
r->options.comparator->FindShortestSeparator(&r->last_key, key);
std::string handle_encoding;
r->pending_handle.EncodeTo(&handle_encoding);
r->index_block.Add(r->last_key, Slice(handle_encoding));
r->pending_index_entry = false;
}

后面的这个if主要是判断是否应该在index block中添加新的项。前面的文章讲过,每个data block对应index block中的一项。assert(r->data_block.empty())用于确定当前的data_block是新的,旧的data_block已经写入到磁盘中了,这说明现在需要为旧的,刚写入磁盘中的那个data block在index block中建一个索引项。每个索引项具有以下形式:

image.png

它和一个data block一一对应。从代码中我们也可以看到,Key是大于他所对应的(旧的)data block中的最大的key,小于当前Data block中的最小的key的最短字符串。offset和size则表示它所对应的data block在sstable中的位置和大小。

至于具体Block的添加函数,比如index_block->Add的实现我们后面再分析。
if (r->filter_block != NULL) {
r->filter_block->AddKey(key);
}

后面这个小的if语句,则是向filter block中添加一个key。filter block是一种metadata block 。filter block的含义我们后面在介绍,这里且不深究。
r->last_key.assign(key.data(), key.size());
r->num_entries++; // sst文件中总记录数
r->data_block.Add(key, value); //加入dataBlock

再往后就是向data_block中添加数据了。首先将当前添加的key设置为last_key,然后增加num_entries,这个数字是统计一共加入了多少个键值对,最后就把key-value加入到data block中。
const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
if (estimated_block_size >= r->options.block_size) {
Flush();
}

最后这几行代码是检查当前的data block中包含的数据量,如果数据量大于某个阀值,则就将它写入磁盘中,形成一个真正意义上的data block。在没写如磁盘中时,它只是存在于内存中而已。

假设当前的data block已经包含了很多数据了,那就会调用Flush写入磁盘,我们看一下Flush函数的实现:
void TableBuilder::Flush() {
Rep* r = rep_;
assert(!r->closed);
if (!ok()) return;
if (r->data_block.empty()) return;
assert(!r->pending_index_entry);
WriteBlock(&r->data_block, &r->pending_handle); //写入到文件中,但可能还在内核缓冲中,没有真正写盘
if (ok()) {
r->pending_index_entry = true;
r->status = r->file->Flush(); //真正写盘了
}
if (r->filter_block != NULL) {
r->filter_block->StartBlock(r->offset);
}
}

这个函数的实现还是很简单的。首先是将data_block中的数据写入到文件中,这里需要注意的是,写入到文件中并不保证写入到磁盘中了,因为可能数据还在内核中缓冲,所以后面还要再调用r->file_Flush,这个函数将会调用fflush_unlocked,将文件的内核缓冲写入磁盘。除此之外,还要设置r->pending_index_entry,这是和Add函数中的写index bloc相对应的,因为此时一个新的data block形成,我们需要为它在index block中生成一个索引项。最后一个是处理filter block。我们且按下不提。
这样TableBuilder.Add函数基本上介绍完毕了,总的来说还是比较简单的,它的功能就是将key-value添加到sstable中,当然实际上是添加到data block中,并且负责更新index block和filter block等管理块。

TableBuilder.Finish函数
前面在介绍TableBuilder.Add函数的时候,我们看到用户通过调用Add函数向sstable中不断添加key-value,这些key-value形成一个个data block,每形成一个data block, TableBuilder都会将这个data block写盘,并同时更新内存中的index block和filter block等管理块,但是我们自始自终都没有看到除了data block之外的其他块有被写盘。那么这些管理块什么时候被写盘的呢?前面我们在介绍sstable的存储格式的时候看到,管理块在文件中的位置都是排在data block的后面,而且随着data block的不断生成管理块是需要不断更新的,因此自然可以想到应该是在不再有key-value需要被写入sstable,即sstable收尾时完成管理块的写盘工作。TableBuilder.Finish就是负责收尾工作。
Finish函数很简单,核心就是WriteBlock,将各个block写入到文件中。这里就不贴Finish的代码了。从Finish的代码结构中我们可以很清晰地看到,TableBuilder最后依次将filter block,metaindex block,index block和footer写入到sstable文件中。

总结
本篇主要介绍了leveldb是怎么将key-value键值对写入磁盘生成sstable文件的,leveldb提供的接口是TableBuilder。TableBuilder将键值对依次写入sstable,形成一个个data block,同时不断更新其他的管理型block,比如index block等。所谓通过TableBuilder的Add函数向sstable中添加数据本质上只是向data_block的缓存中添加数据,后面TableBuilder还需要将data_block的缓存中的数据真正写盘。所以写盘是以块为单位进行的。我们可以认为TableBuilder负责宏观上的sstable的存储格式,主要是以块(block)为单位,而具体的block内部是怎么存储数据的,则是由具体的Block类自己决定。当然,对TableBuilder而言,Block只不过是一个长字符串而已,TableBuilder不关心Block内部的存储细节
后面我们将看block中是怎么存储数据的,我们会看到leveldb为了提高空间效率,用到了一些技巧。

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

推荐阅读更多精彩内容