大白话解析LevelDB: Table

Table

Table就是SST(Sorted Strings Table)

当我们要对一个SST进行读取操作时,比如查找一个Key,或者遍历这个SST,就需要通过Table提供的接口来完成。

TableSST文件的读取细节封装了起来,让上层不需要去关心SST文件的格式和读取细节。

先看下Table的接口定义:

class LEVELDB_EXPORT Table {
   public:
    // 工厂函数,给定一个打开的 SST 文件,构造一个 Table 对象并返回。
    static Status Open(const Options& options, RandomAccessFile* file, uint64_t file_size, Table** table);

    // 创建一个迭代器,用于遍历 SST 中的键值对。
    Iterator* NewIterator(const ReadOptions&) const;

    // 返回一个 key 在 SST 中的大致偏移量。
    uint64_t ApproximateOffsetOf(const Slice& key) const;

    // 从 SST 中查找某个 Key。如果这个 Key 找到了,则调用 handle_result 函数。
    Status InternalGet(const ReadOptions&, const Slice& key, void* arg,
                       void (*handle_result)(void* arg, const Slice& k, const Slice& v));
};

Table::Open

创建一个Table,只需要从SST文件中file中先把footer和``index_block读出来就可以了。构造好index_block,根据footer加载SSTMetaData,一个Table`对象就准备好了

Status Table::Open(const Options& options, RandomAccessFile* file, uint64_t size, Table** table) {
    *table = nullptr;

    // 如果文件大过于小,则判定为文件损坏。
    if (size < Footer::kEncodedLength) {
        return Status::Corruption("file is too short to be an sstable");
    }

    // 1. 先把 footer 读出来。
    char footer_space[Footer::kEncodedLength];
    Slice footer_input;
    Status s = file->Read(size - Footer::kEncodedLength, Footer::kEncodedLength, &footer_input,
                          footer_space);
    if (!s.ok()) return s;

    Footer footer;
    s = footer.DecodeFrom(&footer_input);
    if (!s.ok()) return s;

    // 2. 根据 footer 里的 index_handle 读出 index_block_contents。
    BlockContents index_block_contents;
    ReadOptions opt;
    if (options.paranoid_checks) {
        opt.verify_checksums = true;
    }
    s = ReadBlock(file, opt, footer.index_handle(), &index_block_contents);

    if (s.ok()) {
        // 3. 由 index_block_contents 构建出 index_block。 
        // index_block 构建出来后,就可以创建出一个 Table 对象了。 
        // 等到需要查找某个 Key 的时候,通过 index_block 找到对应的 
        // BlockHandle,然后再读出对应的 Block。
        Block* index_block = new Block(index_block_contents);
        Rep* rep = new Table::Rep;
        rep->options = options;
        rep->file = file;
        rep->metaindex_handle = footer.metaindex_handle();
        rep->index_block = index_block;
        rep->cache_id = (options.block_cache ? options.block_cache->NewId() : 0);
        rep->filter_data = nullptr;
        rep->filter = nullptr;
        *table = new Table(rep);
        (*table)->ReadMeta(footer);
    }

    return s;
}

读取 Footer

Footer位于SST文件的最后,SST的格式可移步参考大白话解析LevelDB:数据格式

所以通过file->Read(size - Footer::kEncodedLength, Footer::kEncodedLength, &footer_input, footer_space)读取SST的末尾,即可读出Footer的内容。

然后通过footer.DecodeFrom(&footer_input)即可创建出对应的Footer对象。

读取 Index Block

构建好footer后,通过footer.index_handle()获取到index_blockBlockHandle,也就是SSTindex_block的偏移量和大小,就可以通过ReadBlock(file, opt, footer.index_handle(), &index_block_contents)读取出index_block的内容了。

然后就可以通过Block* index_block = new Block(index_block_contents)构建出index_block了。

ReadBlock(file, opt, footer.index_handle(), &index_block_contents)的实现如下:

Status ReadBlock(RandomAccessFile* file, const ReadOptions& options, const BlockHandle& handle,
                 BlockContents* result) {
    result->data = Slice();
    result->cachable = false;
    result->heap_allocated = false;

    // handle.offset() 是 Block 在文件中的偏移量,
    // handle.size() 是 Block 的大小,
    // kBlockTrailerSize 是 Block 的尾部信息长度,
    // 包括一个字节的 Block 类型和 4 个字节的 crc 校验和。
    // 根据 handle.offset() 和 handle.size() 可以从 SST 文件中读出 Block 的内容。
    size_t n = static_cast<size_t>(handle.size());
    char* buf = new char[n + kBlockTrailerSize];
    Slice contents;
    Status s = file->Read(handle.offset(), n + kBlockTrailerSize, &contents, buf);
    if (!s.ok()) {
        delete[] buf;
        return s;
    }
    // 检查读出来的 block 的长度是否正确。
    if (contents.size() != n + kBlockTrailerSize) {
        delete[] buf;
        return Status::Corruption("truncated block read");
    }

    // 读出 block 的内容后,校验下 CRC 确保数据没有损坏。
    const char* data = contents.data();  // Pointer to where Read put the data
    if (options.verify_checksums) {
        const uint32_t crc = crc32c::Unmask(DecodeFixed32(data + n + 1));
        const uint32_t actual = crc32c::Value(data, n + 1);
        if (actual != crc) {
            delete[] buf;
            s = Status::Corruption("block checksum mismatch");
            return s;
        }
    }

    // 查看 block 内容是否被压缩,如果被压缩了,就解压缩。
    switch (data[n]) {
        case kNoCompression:
            if (data != buf) {
                delete[] buf;
                result->data = Slice(data, n);
                result->heap_allocated = false;
                result->cachable = false;  // Do not double-cache
            } else {
                result->data = Slice(buf, n);
                result->heap_allocated = true;
                result->cachable = true;
            }

            // Ok
            break;
        case kSnappyCompression: {
            size_t ulength = 0;
            if (!port::Snappy_GetUncompressedLength(data, n, &ulength)) {
                delete[] buf;
                return Status::Corruption("corrupted compressed block contents");
            }
            char* ubuf = new char[ulength];
            if (!port::Snappy_Uncompress(data, n, ubuf)) {
                delete[] buf;
                delete[] ubuf;
                return Status::Corruption("corrupted compressed block contents");
            }
            delete[] buf;
            result->data = Slice(ubuf, ulength);
            result->heap_allocated = true;
            result->cachable = true;
            break;
        }
        default:
            delete[] buf;
            return Status::Corruption("bad block type");
    }

    return Status::OK();
}

创建 Table 对象

构建好index_block后,就可以创建出一个Table对象了。

Rep* rep = new Table::Rep;
rep->options = options;
rep->file = file;
rep->metaindex_handle = footer.metaindex_handle();
rep->index_block = index_block;
rep->cache_id = (options.block_cache ? options.block_cache->NewId() : 0);
rep->filter_data = nullptr;
rep->filter = nullptr;
*table = new Table(rep);

创建Table前,先创建了一个Table::Rep对象,再根据Table::Rep对象创建Table对象。

这属于PIMPL设计模式,将Table的数据成员都封装到Table::Rep中,让Table只暴露出方法接口。PIMPL设计模式的好处可移步参考大白话解析LevelDB:Pimpl模式

加载 SST 的 MetaData

创建Table对象后,还需要先把SSTMeta Block加载进来。

Meta BlockSST里的作用可移步参考大白话解析LevelDB:数据格式

(*table)->ReadMeta(footer);

ReadMeta的实现如下:

void Table::ReadMeta(const Footer& footer) {

    // 目前 LevelDB 里只有一种 Meta Block,就是 Filter Block。 
    // 所以我们就把 Meta Block 直接看成 Filter Block 就行。
    // 如果 options 里没有设置 Filter Policy,那么 Meta Block
    // 也就不需要读取了。
    if (rep_->options.filter_policy == nullptr) {
        return;
    }

    // 只有当 options 里设置了 paranoid_checks 的时候,才会对
    // Meta Block 进行 CRC 校验。
    ReadOptions opt;
    if (rep_->options.paranoid_checks) {
        opt.verify_checksums = true;
    }
    BlockContents contents;
    // 把 Meta Block 的内容读取到 contents 里。
    if (!ReadBlock(rep_->file, opt, footer.metaindex_handle(), &contents).ok()) {
        return;
    }
    // 根据 Meta Block 的内容构建出 Meta Block 对象。
    Block* meta = new Block(contents);

    // 加载 Filter 到 Table 里。
    Iterator* iter = meta->NewIterator(BytewiseComparator());
    std::string key = "filter.";
    key.append(rep_->options.filter_policy->Name());
    iter->Seek(key);
    if (iter->Valid() && iter->key() == Slice(key)) {
        ReadFilter(iter->value());
    }
    delete iter;
    delete meta;
}

ReadMeta的核心为ReadFilter(iter->value())ReadFilter的实现如下:

void Table::ReadFilter(const Slice& filter_handle_value) {
    // 构造出 Filter Block 的 BlockHandle。
    Slice v = filter_handle_value;
    BlockHandle filter_handle;
    if (!filter_handle.DecodeFrom(&v).ok()) {
        return;
    }

    // 根据 options 里的设置,决定是否需要对 Filter Block 进行 CRC 校验。
    ReadOptions opt;
    if (rep_->options.paranoid_checks) {
        opt.verify_checksums = true;
    }
    // 从 SST 文件里读取 Filter Block 的内容。
    BlockContents block;
    if (!ReadBlock(rep_->file, opt, filter_handle, &block).ok()) {
        return;
    }
    if (block.heap_allocated) {
        rep_->filter_data = block.data.data();  // Will need to delete later
    }
    // 根据 Filter Block 的内容构造出 Filter 对象。
    // FilterBlockReader 就是 Filter,名字有点误导性。
    rep_->filter = new FilterBlockReader(rep_->options.filter_policy, block.data);
}

Table::NewIterator

Table::NewIterator实际上就是创建一个TwoLevelIterator

TwoLevelIterator的实现可移步参考大白话解析LevelDB: TwoLevelIterator

Iterator* Table::NewIterator(const ReadOptions& options) const {
    return NewTwoLevelIterator(rep_->index_block->NewIterator(rep_->options.comparator),
                               &Table::BlockReader, const_cast<Table*>(this), options);
}

Table::ApproximateOffsetOf

Table::ApproximateOffsetOf用于返回一个keySST中的大致偏移量。

如果该keySST中,那么返回该key所在的Data BlockSST中的偏移量。

如果该key不在SST中,那么返回一个接近SST末尾的偏移量。

uint64_t Table::ApproximateOffsetOf(const Slice& key) const {
    // 创建一个 index_block 的迭代器。
    Iterator* index_iter = rep_->index_block->NewIterator(rep_->options.comparator);
    // 通过 index_iter 先找到 key 对应的 Data BlockHandle。
    // Data BlockHandle 里包含了 Data Block 的大小和在 SST 文件中的偏移量。
    index_iter->Seek(key);
    uint64_t result;
    if (index_iter->Valid()) {
        // 找到 key 对应的 Data BlockHandle 了。
        BlockHandle handle;
        Slice input = index_iter->value();
        Status s = handle.DecodeFrom(&input);
        if (s.ok()) {
            // 将 Key 所在的 Data Block 在 SST 文件
            // 中的偏移量作为结果返回。
            result = handle.offset();
        } else {
            // 找到了对应 Data BlockHandle,但是解析失败。
            // 那就按没找到 Data BlockHandle 来处理,返回 
            // Meta Block 在 SST 文件中的偏移量,也就是接近
            // SST 文件末尾的一个位置。
            result = rep_->metaindex_handle.offset();
        }
    } else {
        // 没有找到对应的 Data BlockHandle,说明 key 不在
        // 该 SST 里,那就返回 Meta Block 在 SST 文件中的偏移量,
        // 也就是一个接近 SST 文件末尾的位置。
        result = rep_->metaindex_handle.offset();
    }
    delete index_iter;
    return result;
}

Table::InternalGet

Table::InternalGet用于从SST中查找某个Key。如果这个Key找到了,则调用handle_result函数。

Status Table::InternalGet(const ReadOptions& options, const Slice& k, void* arg,
                          void (*handle_result)(void*, const Slice&, const Slice&)) {
    Status s;

    // 创建一个 index_block 的 iterator,通过这个 iterator
    // 找到 key 对应的 Data BlockHandle。
    Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
    iiter->Seek(k);
    if (iiter->Valid()) {
        // 找到对应的 Data BlockHandle了,先通过 filter 判断
        // 一下目标 key 是否在对应的 Data Block 里。
        Slice handle_value = iiter->value();
        FilterBlockReader* filter = rep_->filter;
        BlockHandle handle;
        if (filter != nullptr && handle.DecodeFrom(&handle_value).ok() &&
            !filter->KeyMayMatch(handle.offset(), k)) {
            // 目标 Key 不在对应的 Data Block 里
        } else {
            // 目标 Key 在对应的 Data Block 里,根据
            // Data BlockHandle 找到 Data Block 的
            // 位置和大小,然后读取出 Data Block。
            Iterator* block_iter = BlockReader(this, options, iiter->value());
            // 到 Data Block 中查找目标 Key。
            block_iter->Seek(k);
            // 如果在 Data Block 中查找到目标 Key 了,
            // 就取出 Value,然后 callback handle_resutl。
            if (block_iter->Valid()) {
                (*handle_result)(arg, block_iter->key(), block_iter->value());
            }
            s = block_iter->status();
            delete block_iter;
        }
    }
    if (s.ok()) {
        s = iiter->status();
    }
    delete iiter;
    return s;
}

在 Index Block 里查找 Key 对应的 Data BlockHandle

Index Block不了解的同学可以先喵一眼大白话解析LevelDB:数据格式

先创建一个Index Block的迭代器iiter,然后通过iiter->Seek(k)找到key对应的Data BlockHandle

Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
iiter->Seek(k);

rep_->index_block->NewIterator(rep_->options.comparator)的实现如下,属于工厂模式,将Block::Iter的创建细节封装了起来,对使用者透明(使用者不需要了解Block::Iter的实现细节)。

Iterator* Block::NewIterator(const Comparator* comparator) {
    if (size_ < sizeof(uint32_t)) {
        return NewErrorIterator(Status::Corruption("bad block contents"));
    }
    const uint32_t num_restarts = NumRestarts();
    if (num_restarts == 0) {
        return NewEmptyIterator();
    } else {
        return new Iter(comparator, data_, restart_offset_, num_restarts);
    }
}

Block::Iter的实现感兴趣的同学可以移步参考大白话解析LevelDB: Block Iterator

在对应的 Data Block 里查找目标 Key

如果iiter->Seek(k)找到了key对应的Data BlockHandleData BlockHandle里存储了Data BlockSST文件中的偏移量和大小。

接下来就可以通过BlockReader依据Data BlockHandle里的信息读取出Data Block,然后再在Data Block里查找目标Key

但是在读取Data Block之前,还需要先通过Filter过滤一下,看看目标Key是否在Data Block里。这样可以减少无效的IO开销。

如果没有Filter,目标Key不在Data Block里,我们需要读取了对应的Data Block,才发现目标Key不在里面,这样就浪费了一次IO,读取一次Data BlockIO开销是比较大的。

if (iiter->Valid()) {
    // 找到对应的 Data BlockHandle了,先通过 filter 判断
    // 一下目标 key 是否在对应的 Data Block 里。
    Slice handle_value = iiter->value();
    FilterBlockReader* filter = rep_->filter;
    BlockHandle handle;
    if (filter != nullptr && handle.DecodeFrom(&handle_value).ok() &&
        !filter->KeyMayMatch(handle.offset(), k)) {
        // 目标 Key 不在对应的 Data Block 里
    } else {
        // 目标 Key 在对应的 Data Block 里,根据
        // Data BlockHandle 找到 Data Block 的
        // 位置和大小,然后读取出 Data Block。
        Iterator* block_iter = BlockReader(this, options, iiter->value());
        // 到 Data Block 中查找目标 Key。
        block_iter->Seek(k);
        // 如果在 Data Block 中查找到目标 Key 了,
        // 就取出 Value,然后 callback handle_resutl。
        if (block_iter->Valid()) {
            (*handle_result)(arg, block_iter->key(), block_iter->value());
        }
        s = block_iter->status();
        delete block_iter;
    }
}
  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值