sstable中的Block(table/block.h table/block.cc table/block_builder.h table/block_builder.cc)

sstable中的block由Block类封装并由BlockBuilder类构建。

block的结构为:

entry1 | entry2 | ... | restarts(uint32 * num_of_restarts) | num_of_restarts(uint32) | trailer

其中entry格式为:

shared_bytes(varint32) | unshared_bytes(varint32) | value_bytes(varint32) | unshared_key_data(unshared_bytes) | value_data(value_bytes)

shared_bytes为这个entry中的key与前一个entry的key共享的字节数,entry的key在存储时只存储不共享的部分,即采用前缀压缩的形式,并且前缀压缩是分组进行的,以避免所有查找都需要从头开始的情况。而每个组的起始entry的位置(offset)存储在restarts中,每个为uint32,数量为num_of_restarts。

trailer的格式为:

type(char) | crc(uint32)

type为block的压缩方式,crc为循环冗余校验码。

Block类

Block类对sstable的block中的数据进行封装,并提供了一个迭代器作为接口。Block类的声明为:

class Block
{
  public:
    // ...

  private:
    // ...

    const char *data_;
    size_t size_;
    uint32_t restart_offset_; // Offset in data_ of restart array
    bool owned_;              // Block owns data_[]

    // ...

    class Iter;
};

其中成员变量data_为指向block中数据的指针,size_为block中数据的大小,restart_offset_为block中restarts部分数据的起始位置的offset,owned标记block是否包含data_。

而LevelDB实际上为Block类封装了一个Iter类,对Block的遍历操作需要通过Iter类提供的接口函数进行。

class Block::Iter : public Iterator
{
  private:
    const Comparator *const comparator_;
    const char *const data_;      // underlying block contents
    uint32_t const restarts_;     // Offset of restart array (list of fixed32)
    uint32_t const num_restarts_; // Number of uint32_t entries in restart array

    // current_ is offset in data_ of current entry.  >= restarts_ if !Valid
    uint32_t current_;
    uint32_t restart_index_; // Index of restart block in which current_ falls
    std::string key_;
    Slice value_;
    Status status_;

    // ...

  public:
    // ...

    virtual bool Valid() const { return current_ < restarts_; }
    virtual Status status() const { return status_; }
    virtual Slice key() const
    {
        // ...
    }
    virtual Slice value() const
    {
        // ...
    }

    virtual void Next()
    {
        // ...
    }

    virtual void Prev()
    {
        // ...
    }

    virtual void Seek(const Slice &target)
    {
        // 见下文分析
    }

    virtual void SeekToFirst()
    {
        // ...
    }

    virtual void SeekToLast()
    {
        // ...
    }

  private:
    // ...
};

其中,前几个成员变量的含义与Block类中的成员变量的含义类似,这里不再赘述。key_、value_这两个成员变量为迭代器当前指向的entry的KV值。

在这里主要分析Seek(const Slice &target)函数,其余函数的实现比较简单,在这里就不做分析了。Seek函数首先通过二分查找算法找到target所在的前缀编码分组的位置:

        // Binary search in restart array to find the last restart point
        // with a key < target
        uint32_t left = 0;
        uint32_t right = num_restarts_ - 1;
        while (left < right)
        {
            uint32_t mid = (left + right + 1) / 2;
            uint32_t region_offset = GetRestartPoint(mid);
            uint32_t shared, non_shared, value_length;
            const char *key_ptr = DecodeEntry(data_ + region_offset,
                                              data_ + restarts_,
                                              &shared, &non_shared, &value_length);
            if (key_ptr == nullptr || (shared != 0))
            {
                CorruptionError();
                return;
            }
            Slice mid_key(key_ptr, non_shared);
            if (Compare(mid_key, target) < 0)
            {
                // Key at "mid" is smaller than "target".  Therefore all
                // blocks before "mid" are uninteresting.
                left = mid;
            }
            else
            {
                // Key at "mid" is >= "target".  Therefore all blocks at or
                // after "mid" are uninteresting.
                right = mid - 1;
            }
        }

然后在该分组中遍历:

        // Linear search (within restart block) for first key >= target
        SeekToRestartPoint(left);
        while (true)
        {
            if (!ParseNextKey())
            {
                return;
            }
            if (Compare(key_, target) >= 0)
            {
                return;
            }
        }

其中调用了DecodeEntry函数:

static inline const char *DecodeEntry(const char *p, const char *limit,
                                      uint32_t *shared,
                                      uint32_t *non_shared,
                                      uint32_t *value_length)
{
    // ...
}

这个函数将以p为起始位置的entry解码,并且解码不会超过limit指向的位置,将key共享字节长度、key非共享字节长度、value长度存入shared,non_shared,value_length,返回指向entry中unshared_key_data起始位置的指针。

还调用了ParseNextKey函数:

    bool ParseNextKey()
    {
        // ...
    }

这个函数获取下一个entry的key和value值并存入迭代器的成员变量中。

BlockBuilder类

BlockBuilder类用于构建sstable中的block。BlockBuilder类声明为:

class BlockBuilder
{
  public:
    // ...

    // REQUIRES: Finish() has not been called since the last call to Reset().
    // REQUIRES: key is larger than any previously added key
    void Add(const Slice &key, const Slice &value);

    // Finish building the block and return a slice that refers to the
    // block contents.  The returned slice will remain valid for the
    // lifetime of this builder or until Reset() is called.
    Slice Finish();

    // ...

  private:
    const Options *options_;
    std::string buffer_;             // Destination buffer
    std::vector<uint32_t> restarts_; // Restart points
    int counter_;                    // Number of entries emitted since restart
    bool finished_;                  // Has Finish() been called?
    std::string last_key_;

    // ...
};

其中options_为构建选项,buffer_为实际block中的数据,restarts_存储前缀编码各个分组的起始位置,counter_记录当前前缀编码分组中已经压缩了几个entry,finished_标记Finish是否被调用,last_key_为block中上一次添加的key值。

下面我们首先看Add函数:

void BlockBuilder::Add(const Slice &key, const Slice &value)

Add函数首先检查当前前缀编码分组中压缩的entry个数是否已经达到上限,如果没有达到上限则计算共享的key的字节个数:

    Slice last_key_piece(last_key_);
    assert(!finished_);
    assert(counter_ <= options_->block_restart_interval);
    assert(buffer_.empty() // No values yet?
           || options_->comparator->Compare(key, last_key_piece) > 0);
    size_t shared = 0;
    if (counter_ < options_->block_restart_interval)
    {
        // See how much sharing to do with previous string
        const size_t min_length = std::min(last_key_piece.size(), key.size());
        while ((shared < min_length) && (last_key_piece[shared] == key[shared]))
        {
            shared++;
        }
    }

如果达到上限则重新开始一个restart:

    else
    {
        // Restart compression
        restarts_.push_back(buffer_.size());
        counter_ = 0;
    }
    const size_t non_shared = key.size() - shared;

然后依次将相应的数据编码后组成一个新的entry:

    // Add "<shared><non_shared><value_size>" to buffer_
    PutVarint32(&buffer_, shared);
    PutVarint32(&buffer_, non_shared);
    PutVarint32(&buffer_, value.size());

    // Add string delta to buffer_ followed by value
    buffer_.append(key.data() + shared, non_shared);
    buffer_.append(value.data(), value.size());

最后将数据加在buffer_的后面并更新last_key_和counter_的值:

    // Update state
    last_key_.resize(shared);
    last_key_.append(key.data() + shared, non_shared);
    assert(Slice(last_key_) == key);
    counter_++;

然后是Finish函数:

Slice BlockBuilder::Finish()
{
    // Append restart array
    for (size_t i = 0; i < restarts_.size(); i++)
    {
        PutFixed32(&buffer_, restarts_[i]);
    }
    PutFixed32(&buffer_, restarts_.size());
    finished_ = true;
    return Slice(buffer_);
}

Finish函数首先将restarts_的偏移量存入buffer_,然后存入num_of_restarts,然后将buffer_封装为一个Slice返回。

228 Love u

LevelDB源码分析-sstable的Block的更多相关文章

  1. leveldb源码分析--SSTable之block

    在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSi ...

  2. leveldb源码分析--SSTable之TableBuilder

    上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice ...

  3. leveldb源码分析--SSTable之逻辑结构

    SSTable是leveldb 的核心模块,这也是其称为leveldb的原因,leveldb正是通过将数据分为不同level的数据分为对应的不同的数据文件存储到磁盘之中的.为了理解其机制,我们首先看看 ...

  4. leveldb源码分析--SSTable之Compaction

    对于compaction是leveldb中体量最大的一部分,也应该是最为复杂的部分,为了便于理解我们首先从一些基本的概念开始.下面是一些从doc/impl.html中翻译和整理的内容: Level 0 ...

  5. Leveldb源码分析--1

    coming from http://blog.csdn.net/sparkliang/article/details/8567602 [前言:看了一点oceanbase,没有意志力继续坚持下去了,暂 ...

  6. leveldb源码分析--WriteBatch

    从[leveldb源码分析--插入删除流程]和WriteBatch其名我们就很轻易的知道,这个是leveldb内部的一个批量写的结构,在leveldb为了提高插入和删除的效率,在其插入过程中都采用了批 ...

  7. leveldb源码分析--Key结构

    [注]本文参考了sparkliang的专栏的Leveldb源码分析--3并进行了一定的重组和排版 经过上一篇文章的分析我们队leveldb的插入流程有了一定的认识,而该文设计最多的又是Batch的概念 ...

  8. leveldb源码分析--日志

    我们知道在一个数据库系统中为了保证数据的可靠性,我们都会记录对系统的操作日志.日志的功能就是用来在系统down掉的时候对数据进行恢复,所以日志系统对一个要求可靠性的存储系统是极其重要的.接下来我们分析 ...

  9. leveldb源码分析之Slice

    转自:http://luodw.cc/2015/10/15/leveldb-02/ leveldb和redis这样的优秀开源框架都没有使用C++自带的字符串string,redis自己写了个sds,l ...

随机推荐

  1. Hbuilder护眼主题分享

    sublime还有webstorm有很多主题,但是Hbuilder就相对较少,或者直接说基本没什么主题,在网上搜索了很久也很少有Hbuilder的主题分享,于是就自己取色调了一个仿的护眼主题来分享一下 ...

  2. 1.oracle之表管理sql

    /*数据类型1. number(M,N)   整数位和小数位最多是M,其中小数位为N位2. char(M):定长字符串,长度为M,如果插入数据时长度小于M,则在末尾补上空格3. varchar2(M) ...

  3. C# 之TripleDESCryptoServiceProvider类加密/解密程序

    这篇博文的编写基于以下博客地址提供的知识: TripleDESCryptoServiceProvider 加密解密 基于该博客,我的毕业设计中密码存储加密字符串这一问题得到解决.

  4. [转]C++11常用特性的使用经验总结

    转载出处 http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++1 ...

  5. C#缓存流的使用浅析

    C#缓存流的使用实例:用缓存流复制文件,C#文件处理操作必须先导入命名空间: using System.IO; ///在按钮的Click事件中添加如下代码: private void button1_ ...

  6. Go Example--排序

    package main import ( "fmt" "sort" ) func main() { strs := []string{"c" ...

  7. python 文件读写模式r,r+,w,w+,a,a+的区别(附代码示例)

    如下表   模式 可做操作 若文件不存在 是否覆盖 r 只能读 报错 - r+ 可读可写 报错 是 w 只能写 创建 是 w+ 可读可写 创建 是 a 只能写 创建 否,追加写 a+ 可读可写 创建 ...

  8. Guava 6:Concurrency

    一.引子 有点经验的工程师一定对多线程比较熟悉,JDK封装的FutureTask实现了这一功能.如下图: FutureTask实现了RunnableFuture接口,而RunnableFuture接口 ...

  9. Custom Grid Columns - FireMonkey Guide

    原文 http://monkeystyler.com/guide/Custom-Grid-Columns ack to FireMonkey Topics As we saw in TGrid a F ...

  10. 【C++】boost::shared_ptr boost::make_shared

    一.shared_ptr shared_ptr作为一个动态分配的对象,当最后一个指向其内容的指针销毁(destroyed)或重置(reset),其指向的内容会被销毁(deleted).不再需要显式调用 ...