leveldb源码分析2

leveldb源码分析2 本系列《leveldb源码分析》共有22篇文章,这是第二篇。 3.Int Coding 轻松一刻,前面约定中讲过Leveldb使用了很多VarInt型编码,典型的如后面将涉及到的各种key。其中的编码、解码函数分为VarInt和FixedInt两种。int32和int64操作都是类似的。 3.1 Decode 首先是FixedInt编码,直接上代码,很简单明了。 void EncodeFixed32(char* buf, uint32_t value) { if (port::kLittleEndian) { memcpy(buf, &value,sizeof(value)); } else { buf[0] = value & 0xff; buf[1] = (value >> 8)& 0xff; buf[2] = (value >> 16)& 0xff; buf[3] = (value >> 24)& 0xff; } } 下面是VarInt编码,int32和int64格式,代码如下,有效位是7bit的,因此把uint32按7bit分割,对unsigned char赋值时,超出0xFF会自动截断,因此直接*(ptr++) = v|B即可,不需要再把(v|B)与0xFF作&操作。 char* EncodeVarint32(char* dst, uint32_t v) { unsigned char* ptr =reinterpret_cast<unsigned char*>(dst); static const int B = 128; if (v < (1<<7)) { *(ptr++) = v; } else if (v < (1<<14)){ *(ptr++) = v | B; *(ptr++) = v>>7; } else if (v < (1<<21)){ *(ptr++) = v | B; *(ptr++) = (v>>7) | B; *(ptr++) = v>>14; } else if (v < (1<<28)){ *(ptr++) = v | B; *(ptr++) = (v>>7) | B; *(ptr++) = (v>>14) | B; *(ptr++) = v>>21; } else { *(ptr++) = v | B; *(ptr++) = (v>>7) | B; *(ptr++) = (v>>14) | B; *(ptr++) = (v>>21) | B; *(ptr++) = v>>28; } return reinterpret_cast<char*>(ptr); } // 对于uint64,直接循环 char* EncodeVarint64(char* dst, uint64_t v) { static const int B = 128; unsigned char* ptr =reinterpret_cast<unsigned char*>(dst); while (v >= B) { *(ptr++) = (v & (B-1)) |B; v >>= 7; } *(ptr++) =static_cast<unsigned char>(v); returnreinterpret_cast<char*>(ptr); } 3....

January 11, 2021

leveldb源码分析20

leveldb源码分析20 本系列《leveldb源码分析》共有22篇文章,这是第二十篇 12 DB的打开 先分析LevelDB是如何打开db的,万物始于创建。在打开流程中有几个辅助函数:DBImpl(),DBImpl::Recover, DBImpl::DeleteObsoleteFiles, DBImpl::RecoverLogFile, DBImpl::MaybeScheduleCompaction。 12.1 DB::Open() 打开一个db,进行PUT、GET操作,就是前面的静态函数DB::Open的工作。如果操作成功,它就返回一个db指针。前面说过DB就是一个接口类,其具体实现在DBImp类中,这是一个DB的子类。 函数声明为: Status DB::Open(const Options& options, const std::string&dbname, DB** dbptr); 分解来看,Open()函数主要有以下5个执行步骤。 S1 创建DBImpl对象,其后进入**DBImpl::Recover()函数执行S2和S3。 S2 从已存在的db文件恢复db数据,根据CURRENT记录的MANIFEST文件读取db元信息;这通过调用VersionSet::Recover()完成。 S3 然后过滤出那些最近的更新log,前一个版本可能新加了这些log,但并没有记录在MANIFEST中。然后依次根据时间顺序,调用DBImpl::RecoverLogFile()从旧到新回放这些操作log。回放log时可能会修改db元信息,比如dump了新的level 0文件,因此它将返回一个VersionEdit对象,记录db元信息的变动。 S4 如果DBImpl::Recover()返回成功,就执行VersionSet::LogAndApply()**应用VersionEdit,并保存当前的DB信息到新的MANIFEST文件中。 S5 最后删除一些过期文件,并检查是否需要执行compaction,如果需要,就启动后台线程执行。 下面就来具体分析Open函数的代码,在Open函数中涉及到上面的3个流程。 S1 首先创建DBImpl对象,锁定并试图做Recover操作。Recover操作用来处理创建flag,比如存在就返回失败等等,尝试从已存在的sstable文件恢复db。并返回db元信息的变动信息,一个VersionEdit对象。 1DBImpl* impl = newDBImpl(options, dbname); 2impl->mutex_.Lock(); // 锁db 3VersionEdit edit; 4Status s =impl->Recover(&edit); // 处理flag&恢复:create_if_missing,error_if_exists S2 如果Recover返回成功,则调用VersionSet取得新的log文件编号——实际上是在当前基础上+1,准备新的log文件。如果log文件创建成功,则根据log文件创建log::Writer。然后执行VersionSet::LogAndApply,根据edit记录的增量变动生成新的current version,并写入MANIFEST文件。 函数NewFileNumber(){returnnext_file_number_++;},直接返回next_file_number_。 1uint64_t new_log_number = impl->versions_->NewFileNumber(); 2WritableFile* lfile; 3s = options.env->NewWritableFile(LogFileName(dbname, new_log_number), &lfile); 4if (s.ok()) { 5 edit.SetLogNumber(new_log_number); 6 impl->logfile_ = lfile; 7 impl->logfile_number_ = new_log_number; 8 impl->log_ = newlog::Writer(lfile); 9 s = impl->versions_->LogAndApply(&edit, &impl->mutex_); 10} S3 如果VersionSet::LogAndApply返回成功,则删除过期文件,检查是否需要执行compaction,最终返回创建的DBImpl对象。 1if (s.ok()) { 2 impl->DeleteObsoleteFiles(); 3 impl->MaybeScheduleCompaction(); 4} 5impl->mutex_.Unlock(); 6if (s....

January 11, 2021

leveldb源码分析21

leveldb源码分析21 本系列《leveldb源码分析》共有22篇文章,这是第二十一篇 14 DB的查询与遍历之1 分析完如何打开和关闭db,本章就继续分析如何从db中根据key查询value,以及遍历整个db。 14.1 Get() 函数声明:StatusGet(const ReadOptions& options, const Slice& key, std::string* value) 从DB中查询key 对应的value,参数@options指定读取操作的选项,典型的如snapshot号,从指定的快照中读取。快照本质上就是一个sequence号,后面将单独在快照一章中分析。 下面就来分析下函数逻辑: // S1 锁mutex,防止并发,如果指定option则尝试获取snapshot;然后增加MemTable的引用值。 MutexLock l(&mutex_); SequenceNumber snapshot; if (options.snapshot != NULL) snapshot = reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_; else snapshot = versions_->LastSequence(); // 取当前版本的最后Sequence MemTable *mem = mem_, *imm = imm_; Version* current = versions_->current(); mem->Ref(); if (imm != NULL) imm->Ref(); current->Ref(); // S2 从sstable文件和MemTable中读取时,释放锁mutex;之后再次锁mutex。 bool have_stat_update = false; Version::GetStats stats; { mutex_.Unlock(); // 先从memtable中查询,再从immutable memtable中查询 LookupKey lkey(key, snapshot); if (mem->Get(lkey, value, &s)) { } else if (imm != NULL && imm->Get(lkey, value, &s)) { } else { // 需要从sstable文件中查询 s = current->Get(options, lkey, value, &stats); have_stat_update = true; // 记录之,用于compaction } mutex_....

January 11, 2021

leveldb源码分析22

leveldb源码分析22 本系列《leveldb源码分析》共有22篇文章,这是第二十二篇 14 DB的查询与遍历之2 14.4 DBIter Leveldb数据库的MemTable和sstable文件的存储格式都是**(user key, seq, type) => uservalue**。DBIter把同一个userkey在DB中的多条记录合并为一条,综合考虑了userkey的序号、删除标记、和写覆盖等等因素。 从前面函数NewIterator的代码还能看到,DBIter内部使用了MergingIterator,在调用MergingItertor的系列seek函数后,DBIter还要处理key的删除标记。否则,遍历时会把已删除的key列举出来。 DBIter还定义了两个移动方向,默认是kForward: 1) kForward,向前移动,代码保证此时DBIter的内部迭代器刚好定位在this->key(),this->value()这条记录上; 2) kReverse,向后移动,代码保证此时DBIter的内部迭代器刚好定位在所有key=this->key()的entry之前。 其成员变量savedkey和saved value保存的是KReverse方向移动时的k/v对,每次seek系调用之后,其值都会跟随iter_而改变。 DBIter的代码开始读来感觉有些绕,主要就是它要处理删除标记,而且其底层的MergingIterator,对于同一个key会有多个不同sequence的entry。导致其Next/Prev操作比较复杂,要考虑到上一次移动的影响,跳过删除标记和重复的key。 DBIter必须导出Iterator定义的几个接口,下面就拖出来挨个分析。 14.4.1 Get系接口 首先是几个简单接口,获取key、value和status的: //kForward直接取iter_->value(),否则取saved value virtual Slice value() const { assert(valid_); return (direction_ == kForward) ? iter_->value() : saved_value_; } virtual Status status() const { if (status_.ok()) returniter_->status(); return status_; } 14.4.2 辅助函数 在分析seek系函数之前,先来理解两个重要的辅助函数:FindNextUserEntry和FindPrevUserEntry的功能和逻辑。其功能就是循环跳过下一个/前一个delete的记录,直到遇到kValueType的记录。 先来看看,函数声明为: void DBIter::FindNextUserEntry(bool skipping, std::string* skip) 参数@skipping表明是否要跳过sequence更小的entry; 参数@skip临时存储空间,保存seek时要跳过的key; 在进入FindNextUserEntry时,iter_刚好定位在this->key(), this->value()这条记录上。下面来看函数实现: virtual Slice key() const { //kForward直接取iter_->key(),否则取saved key assert(valid_); return (direction_ == kForward) ? ExtractUserKey(iter_->key()) : saved_key_; } // 循环直到找到合适的entry,direction必须是kForward assert(iter_->Valid()); assert(direction_ == kForward); do { ParsedInternalKey ikey; // 确保iter_->key()的sequence <= 遍历指定的sequence if (ParseKey(&ikey) && ikey....

January 11, 2021

leveldb源码分析3

leveldb源码分析3 本系列《leveldb源码分析》共有22篇文章,这是第三篇。 4. Memtable之一 Memtable是leveldb很重要的一块,leveldb的核心之一。我们肯定关注KV数据在Memtable中是如何组织的,秘密在Skip list中。 4.1 用途 在Leveldb中,所有内存中的KV数据都存储在Memtable中,物理disk则存储在SSTable中。在系统运行过程中,如果Memtable中的数据占用内存到达指定值(Options.write_buffer_size),则Leveldb就自动将Memtable转换为Memtable,并自动生成新的Memtable,也就是Copy-On-Write机制了。 Immutable Memtable则被新的线程Dump到磁盘中,Dump结束则该Immutable Memtable就可以释放了。因名知意,Immutable Memtable是只读的。 所以可见,最新的数据都是存储在Memtable中的,Immutable Memtable和物理SSTable则是某个时点的数据。 为了防止系统down机导致内存数据Memtable或者Immutable Memtable丢失,leveldb自然也依赖于log机制来保证可靠性了。 Memtable提供了写入KV记录,删除以及读取KV记录的接口,但是事实上**Memtable并不执行真正的删除操作,**删除某个Key的Value在Memtable内是作为插入一条记录实施的,但是会打上一个Key的删除标记,真正的删除操作在后面的 Compaction过程中,lazy delete。 4.2 核心是Skip list 另外,Memtable中的KV对是根据Key排序的,leveldb在插入等操作时保证key的有序性。想想,前面看到的Skip list不正是合适的人选吗,因此Memtable的核心数据结构是一个Skip list,Memtable只是一个接口类。当然随之而来的一个问题就是Skip list是如何组织KV数据对的,在后面分析Memtable的插入、查询接口时我们将会看到答案。 4.3 接口说明 先来看看Memtable的接口: void Ref() { ++refs_; } void Unref(); Iterator* NewIterator(); void Add(SequenceNumber seq, ValueType type, const Slice& key, const Slice& value); bool Get(const LookupKey& key, std::string* value, Status* s); 首先Memtable是基于引用计数的机制,如果引用计数为0,则在Unref中删除自己,Ref和Unref就是干这个的。 NewIterator是返回一个迭代器,可以遍历访问table的内部数据,很好的设计思想,这种方式隐藏了table的内部实现。外部调用者必须保证使用Iterator访问Memtable的时候该Memtable是live的。 Add和Get是添加和获取记录的接口,没有Delete,还记得前面说过,memtable的delete实际上是插入一条type为kTypeDeletion的记录。 4.4 类图 先来看看Memtable相关的整体类层次吧,并不复杂,还是相当清晰的。见图4.4-1。 4.5 Key结构 Memtable是一个KV存储结构,那么这个key肯定是个重点了,在分析接口实现之前,有必要仔细分析一下Memtable对key的使用。 这里面有5个key的概念,可能会让人混淆,下面就来一个一个的分析。 4.5.1 InternalKey & ParsedInternalKey & User Key InternalKey是一个复合概念,是有几个部分组合成的一个key,ParsedInternalKey就是对InternalKey分拆后的结果,先来看看ParsedInternalKey的成员,这是一个struct: Slice user_key; SequenceNumber sequence; ValueType type; 也就是说InternalKey是由User key + SequenceNumber + ValueType组合而成的,顺便先分析下几个Key相关的函数,它们是了解Internal Key和User Key的关键。 首先是InternalKey和ParsedInternalKey相互转换的两个函数,如下。 bool ParseInternalKey (const Slice& internal_key, ParsedInternalKey* result); void AppendInternalKey (std::string* result, const ParsedInternalKey& key); 函数实现很简单,就是字符串的拼接与把字符串按字节拆分,代码略过。根据实现,容易得到InternalKey的格式为:...

January 11, 2021

leveldb源码分析4

leveldb源码分析4 本系列《leveldb源码分析》共有22篇文章,这是第四篇 4.Memtable之2 4.6 Comparator 弄清楚了key,接下来就要看看key的使用了,先从Comparator开始分析。首先Comparator是一个抽象类,导出了几个接口。 其中**Name()和Compare()**接口都很明了,另外的两个Find xxx接口都有什么功能呢,直接看程序注释: //Advanced functions: these are used to reduce the space requirements //for internal data structures like index blocks. // 这两个函数:用于减少像index blocks这样的内部数据结构占用的空间 // 其中的*start和*key参数都是IN OUT的。 //If *start < limit, changes *start to a short string in [start,limit). //Simple comparator implementations may return with *start unchanged, //i.e., an implementation of this method that does nothing is correct. // 这个函数的作用就是:如果*start < limit,就在[startlimit,)中找到一个 // 短字符串,并赋给*start返回 // 简单的comparator实现可能不改变*start,这也是正确的 virtual void FindShortestSeparator(std::string* start, const Slice& limit) const = 0; //Changes *key to a short string >= *key. //Simple comparator implementations may return with *key unchanged, //i.e., an implementation of this method that does nothing is correct....

January 11, 2021

leveldb源码分析5

leveldb源码分析5 本系列《leveldb源码分析》共有22篇文章,这是第五篇。 5.操作Log 1 分析完KV在内存中的存储,接下来就是操作日志。所有的写操作都必须先成功的append到操作日志中,然后再更新内存memtable。这样做有两点: 可以将随机的写IO变成append,极大的提高写磁盘速度; 防止在节点down机导致内存数据丢失,造成数据丢失,这对系统来说是个灾难。 在各种高效的存储系统中,这已经是口水技术了。 5.1 格式 在源码下的文档doc/log_format.txt中,作者详细描述了log格式: The log file contents are a sequence of 32KB blocks. The only exception is that the tail of thefile may contain a partial block. Each block consists of a sequence of records: block:= record* trailer? record := checksum: uint32 // crc32c of type and data[] ; little-endian length: uint16 // little-endian type: uint8 // One of FULL,FIRST, MIDDLE, LAST data: uint8[length] A record never starts within the last six bytes of a block (since it won’tfit). Any leftover bytes here form thetrailer, which must consist entirely of zero bytes and must be skipped byreaders....

January 11, 2021

leveldb源码分析6

leveldb源码分析6 本系列《leveldb源码分析》共有22篇文章,这是第六篇。 5. 操作Log 2 5.3 读日志 日志读取显然比写入要复杂,要检查checksum,检查是否有损坏等等,处理各种错误。 5.3.1 类层次 Reader主要用到了两个接口,一个是汇报错误的Reporter,另一个是log文件读取类SequentialFile。 Reporter的接口只有一个: void Corruption(size_t bytes,const Status& status); SequentialFile有两个接口: Status Read(size_t n, Slice* result, char* scratch); Status Skip(uint64_t n); 说明下,Read接口有一个result参数传递结果就行了,为何还有一个scratch呢,这个就和Slice相关了。它的字符串指针是传入的外部char*指针,自己并不负责内存的管理与分配。因此Read接口需要调用者提供一个字符串指针,实际存放字符串的地方。 Reader类有几个成员变量,需要注意: bool eof_; // 上次Read()返回长度< kBlockSize,暗示到了文件结尾EOF uint64_t last_record_offset_; // 函数ReadRecord返回的上一个record的偏移 uint64_t end_of_buffer_offset_;// 当前的读取偏移 uint64_t const initial_offset_;// 偏移,从哪里开始读取第一条record Slice buffer_; // 读取的内容 5.3.2日志读取流程 Reader只有一个接口,那就是ReadRecord,下面来分析下这个函数。 S1 根据initial offset跳转到调用者指定的位置,开始读取日志文件。跳转就是直接调用SequentialFile的Seek接口。 另外,需要先调整调用者传入的initialoffset参数,调整和跳转逻辑在SkipToInitialBlock函数中。 if (last_record_offset_ <initial_offset_) { // 当前偏移 < 指定的偏移,需要Seek if (!SkipToInitialBlock()) return false; } 下面的代码是SkipToInitialBlock函数调整read offset的逻辑: // 计算在block内的偏移位置,并圆整到开始读取block的起始位置 size_t offset_in_block =initial_offset_ % kBlockSize; uint64_t block_start_location =initial_offset_ - offset_in_block; // 如果偏移在最后的6byte里,肯定不是一条完整的记录,跳到下一个block if (offset_in_block >kBlockSize - 6) { offset_in_block = 0; block_start_location +=kBlockSize; } end_of_buffer_offset_ =block_start_location; // 设置读取偏移 if (block_start_location > 0) file_->Skip(block_start_location); // 跳转 首先计算出在block内的偏移位置,然后圆整到要读取block的起始位置。开始读取日志的时候都要保证读取的是完整的block,这就是调整的目的。...

January 11, 2021

leveldb源码分析7

leveldb源码分析7 本系列《leveldb源码分析》共有22篇文章,这是第七篇。 6. SSTable之一 SSTable是Leveldb的核心之一,是表数据最终在磁盘上的物理存储。也是体量比较大的模块。 6.1 SSTable的文件组织 作者在文档doc/table_format.txt中描述了表的逻辑结构,如图6.1-1所示。逻辑上可分为两大块,数据存储区Data Block,以及各种Meta信息。 文件中的k/v对是有序存储的,并且被划分到连续排列的Data Block里面,这些Data Block从文件头开始顺序存储,Data Block的存储格式代码在block_builder.cc中; 紧跟在Data Block之后的是Meta Block,其格式代码也在block_builder.cc中;Meta Block存储的是Filter信息,比如Bloom过滤器,用于快速定位key是否在data block中。 MetaIndex Block是对Meta Block的索引,它只有一条记录,key是meta index的名字(也就是Filter的名字),value为指向meta index的BlockHandle;BlockHandle是一个结构体,成员offset_是Block在文件中的偏移,成员size_是block的大小; Index block是对Data Block的索引,对于其中的每个记录,其key >=Data Block最后一条记录的key,同时<其后Data Block的第一条记录的key;value是指向data index的BlockHandle; Footer,文件的最后,大小固定,其格式如图6.1-2所示。 成员metaindex_handle指出了meta index block的起始位置和大小; 成员index_handle指出了index block的起始地址和大小; 这两个字段都是BlockHandle对象,可以理解为索引的索引,通过Footer可以直接定位到metaindex和index block。再后面是一个填充区和魔数(0xdb4775248b80fb57)。 6.2 Block存储格式 6.2.1 Block的逻辑存储 Data Block是具体的k/v数据对存储区域,此外还有存储meta的metaIndex Block,存储data block索引信息的Index Block等等,他们都是以Block的方式存储的。来看看Block是如何组织的。每个Block有三部分构成:block data, type, crc32,如图6.2-1所示。 类型type指明使用的是哪种压缩方式,当前支持none和snappy压缩。 虽然block有好几种,但是Block Data都是有序的k/v对,因此写入、读取BlockData的接口都是统一的,对于Block Data的管理也都是相同的。 对Block的写入、读取将在创建、读取sstable时分析,知道了格式之后,其读取写入代码都是很直观的。 由于sstable对数据的存储格式都是Block,因此在分析sstable的读取和写入逻辑之前,我们先来分析下Leveldb对Block Data的管理。 Leveldb对Block Data的管理是读写分离的,读取后的遍历查询操作由Block类实现,BlockData的构建则由BlockBuilder类实现。 6.2.2 重启点-restartpoint BlockBuilder对key的存储是前缀压缩的,对于有序的字符串来讲,这能极大的减少存储空间。但是却增加了查找的时间复杂度,为了兼顾查找效率,每隔K个key,leveldb就不使用前缀压缩,而是存储整个key,这就是重启点(restartpoint)。 在构建Block时,有参数Options::block_restart_interval定每隔几个key就直接存储一个重启点key。 Block在结尾记录所有重启点的偏移,可以二分查找指定的key。Value直接存储在key的后面,无压缩。 对于一个k/v对,其在block中的存储格式为: 共享前缀长度 shared_bytes: varint32 前缀之后的字符串长度 unshared_bytes: varint32 值的长度 value_length: varint32 前缀之后的字符串 key_delta: char[unshared_bytes] 值 value: char[value_length] 对于重启点,shared_bytes= 0 Block的结尾段格式是: > restarts: uint32[num_restarts] > num_restarts: uint32 // 重启点个数 **元素restarts[i]**存储的是block的第i个重启点的偏移。很明显第一个k/v对,总是第一个重启点,也就是restarts[0] = 0; 图6.2-2给出了block的存储示意图。 总体来看Block可分为k/v存储区和后面的重启点存储区两部分,其中k/v的存储格式如前面所讲,可看做4部分: 前缀压缩的key长度信息 + value长度 + key前缀之后的字符串+ value...

January 11, 2021

leveldb源码分析8

leveldb源码分析8 本系列《leveldb源码分析》共有22篇文章,这是第八篇 6 SSTable之2 6.4 创建sstable文件 了解了sstable文件的存储格式,以及Data Block的组织,下面就可以分析如何创建sstable文件了。相关代码在table_builder.h/.cc以及block_builder.h/.cc(构建Block)中。 6.4.1 TableBuilder类 构建sstable文件的类是TableBuilder,该类提供了几个有限的方法可以用来添加k/v对,Flush到文件中等等,它依赖于BlockBuilder来构建Block。 TableBuilder的几个接口说明下: void Add(const Slice& key, const Slice& value),向当前正在构建的表添加新的{key, value}对,要求根据Option指定的Comparator,key必须位于所有前面添加的key之后; void Flush(),将当前缓存的k/v全部flush到文件中,一个高级方法,大部分的client不需要直接调用该方法; void Finish(),结束表的构建,该方法被调用后,将不再会使用传入的WritableFile; void Abandon(),结束表的构建,并丢弃当前缓存的内容,该方法被调用后,将不再会使用传入的WritableFile;【只是设置closed为true,无其他操作】 一旦**Finish()/Abandon()**方法被调用,将不能再次执行Flush或者Add操作。 下面来看看涉及到的类,如图6.3-1所示。 图6.3-1 其中WritableFile和op log一样,使用的都是内存映射文件。Options是一些调用者可设置的选项。 TableBuilder只有一个成员变量Rep* rep_,实际上Rep结构体的成员就是TableBuilder所有的成员变量;这样做的目的,可能是为了隐藏其内部细节。Rep的定义也是在.cc文件中,对外是透明的。 简单解释下成员的含义: Options options; // data block的选项 Options index_block_options; // index block的选项 WritableFile* file; // sstable文件 uint64_t offset; // 要写入data block在sstable文件中的偏移,初始0 Status status; //当前状态-初始ok BlockBuilder data_block; //当前操作的data block BlockBuilder index_block; // sstable的index block std::string last_key; //当前data block最后的k/v对的key int64_t num_entries; //当前data block的个数,初始0 bool closed; //调用了Finish() or Abandon(),初始false FilterBlockBuilder*filter_block; //根据filter数据快速定位key是否在block中 bool pending_index_entry; //见下面的Add函数,初始false BlockHandle pending_handle; //添加到index block的data block的信息 std::string compressed_output;//压缩后的data block,临时存储,写入后即被清空 Filter block是存储的过滤器信息,它会存储{key, 对应data block在sstable的偏移值},不一定是完全精确的,以快速定位给定key是否在data block中。 下面分析如何向sstable中添加k/v对,创建并持久化sstable。其它函数都比较简单,略过。另外对于Abandon,简单设置closed=true即返回。 6.4.2 添加k/v对 这是通过方法**Add(constSlice& key, const Slice& value)**完成的,没有返回值。下面分析下函数的逻辑:...

January 11, 2021