美文网首页
LevelDB MemTable

LevelDB MemTable

作者: wayyyy | 来源:发表于2024-02-12 21:20 被阅读0次
MemTable

前面进行操作流程介绍,API 进行读取时首先读取 MemTable,然后读取 Immutable MemTable,接着读取 SSTable;
而进行写入时会先写入 WALLog,然后写入 MemTable。
读写都会涉及 MemTable 模块,WALLog 文件和 MemTable 文件是一一对应的关系,只有当 MemTable 大小超过设定的阈值,成功生成 SSTable 且刷新到磁盘之后才会将对应的 WALLog 文件删除,此时会开始使用一个新的 MemTable。如果涉及崩溃恢复,则需要从 WALLog文件恢复一个 MemTable 文件。

image.png

每个LevelDB实例最多会维护 2个MemTable:

  1. mem_
  2. Immutable MemTable:imm_

mem_ 可以读写,imm_ 只读,最新写入的数据都会保存到 mem_ 中。当 mem_ 的大小超过 write_buffer_size 时,LevelDB 就会将其切换成 imm_,并生成新的 mem_。 LevelDB 的后台线程会将 imm_ compact 成 SSTable 保存在磁盘上。 如果前台的写入速度很快,有可能出现 mem_ 的大小已经超过 write_buffer_size,但是前一个 imm_ 还没有被 compact 到磁盘上,无法切换 MemTable,此时就会阻塞写请求。

MemTable 主要支持的操作有:

  • 插入单条记录(Add)
  • 查询单条记录(Get)
  • 遍历查询,生成一个迭代器,该迭代器可以遍历 MemTable

LevelDB 的 MemTable 的主要功能是将:内部编码、内存分配(Arena)和 SkipList 封装在一起。具体代码详细见:db/memtable.h 和 db/memtable.cc

成员,构造函数,析构函数
class MemTable
{
public:
    MemTable::MemTable(const InternalKeyComparator &comparator) 
        : comparator_(comparator), refs_(0), table_(comparator_, &arena_) 
    {
    }

private:
    MemTable::~MemTable() { assert(refs_ == 0); }
  
private:
    friend class MemTableIterator;
    struct KeyComparator
    {
        const InternalKeyComparator comparator;
        explicit KeyComparator(const InternalKeyComparator &c) : comparator(c) {}
        int operator()(const char *a, const char *b) const;
    };

    typedef SkipList<const char *, KeyComparator> Table;

    KeyComparator   comparator_;
    int             refs_;
    Arena           arena_;
    Table           table_;    // 对skiplist的封装
}
MemTable 插入

插入通过调用Add方法实现。主要计算该次插入需要的内存大小,分配内存并将数据按照如下面图示的顺序写入,然后调用SkipList的插入方法将数据插入。

typedef uint64_t SequenceNumber;

enum ValueType
{
    kTypeDeletion = 0x0,
    kTypeValue = 0x1
};

void MemTable::Add(SequenceNumber s, ValueType type, const Slice &key, const Slice &value)
{
    size_t key_size = key.size();
    size_t val_size = value.size();
    size_t internal_key_size = key_size + 8;
    const size_t encoded_len = VarintLength(internal_key_size) +
                                   internal_key_size + VarintLength(val_size) +
                                   val_size;
    char *buf = arena_.Allocate(encoded_len);
    char *p = EncodeVarint32(buf, internal_key_size);
    std::memcpy(p, key.data(), key_size);
    p += key_size;  EncodeFixed64(p, (s << 8) | type);
    p += 8;  p = EncodeVarint32(p, val_size);
    std::memcpy(p, value.data(), val_size);
    assert(p + val_size == buf + encoded_len);
    table_.Insert(buf);
}

LevelDB 对于元素的删除是采用惰性删除法,标记ValueType == kTypeDeletion
MemTbale 存储 key-value 格式如下:

image.png
  • kLength 变长的 32 位整数 varint 的编码,表示 internal_key 的长度。
  • internal_key
    由user_key + seq(7字节) + type(1字节)组成,user_key 表示上层写入的 key,比如"hello" "world" 键值对,那么user_key 表示"hello"
    • seq
    • type
  • vLength
    变长的 32 位整数 varint 的编码,表示user_value的长度。
  • user_value
Get 接口
bool MemTable::Get(const LookupKey &key, std::string *value, Status *s)
{
    Slice memkey = key.memtable_key();
    // 生成一个 SkipList 迭代器
    Table::Iterator iter(&table_);
    // 在 SkipList 中查找
    iter.Seek(memkey.data());
    if (iter.Valid())
    {
        const char *entry = iter.key();
        uint32_t key_length;
        // 获取key的值
        const char *key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
        if (comparator_.comparator.user_comparator()->Compare(Slice(key_ptr, key_length - 8), key.user_key()) == 0)
        {
            // 如果判断key的值完全相同,则继续取出 ValueType,判断是否已经删除
            const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
            switch (static_cast<ValueType>(tag & 0xff))
            {
            case kTypeValue:
            {
                // 如果判断 ValueType 为增加一个键值对,则取出值并返回为 true
                Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
                value->assign(v.data(), v.size());
                return true;
            }
            case kTypeDeletion:
                // 如果判断 ValueType 为删除一个键值对,则将状态值赋值为 NotFound,并且返回为 true
                *s = Status::NotFound(Slice());
                return true;
            }
        }
    }
    return false;
}
MemTable 迭代器

迭代器通过 NewIterator 方法生成,生成迭代器之后便可以遍历该 MemTable。

Iterator *MemTable::NewIterator() { return new MemTableIterator(&table_); }

使用 NewIterator 方法生成一个 MemTable 迭代器,实际返回的是一个 MemTableIterator 类,生成该类时会将 的table_成员变量传入MemTableIterator 构造函数

class MemTableIterator : public Iterator
{
public:
    explicit MemTableIterator(MemTable::Table *table) : iter_(table) {}

    MemTableIterator(const MemTableIterator &) = delete;
    MemTableIterator &operator=(const MemTableIterator &) = delete;

    ~MemTableIterator() override = default;

    // 判断迭代器是否值相一个数据,或非法
    bool Valid() const override { return iter_.Valid(); }
    //  将迭代器指向集合中 key 为 k 的元素
    void Seek(const Slice &k) override { iter_.Seek(EncodeKey(&tmp_, k)); }
    // 将迭代器指向集合中的第一个元素
    void SeekToFirst() override { iter_.SeekToFirst(); }
    // 将迭代器指向集合中的最后一个元素
    void SeekToLast() override { iter_.SeekToLast(); }
    // 将迭代器指向下一个元素
    void Next() override { iter_.Next(); }
    // 将迭代器指向前一个元素
    void Prev() override { iter_.Prev(); }
    // 返回迭代器目前指向的元素的 key
    Slice key() const override { return GetLengthPrefixedSlice(iter_.key()); }
    // 返回迭代器目前指向元素的 data
    Slice value() const override
    {
        Slice key_slice = GetLengthPrefixedSlice(iter_.key());
        return GetLengthPrefixedSlice(key_slice.data() + key_slice.size());
    }

    Status status() const override { return Status::OK(); }

private:
    MemTable::Table::Iterator iter_;
    std::string tmp_;
};

可以看到,MemTable 迭代器的方法实际都是调用自 SkipList 中的迭代器。

KeyComparator
struct KeyComparator
{
    const InternalKeyComparator comparator;
    explicit KeyComparator(const InternalKeyComparator &c) : comparator(c) {}
    int operator()(const char *a, const char *b) const;
};

class InternalKeyComparator : public Comparator
{
    private:
        const Comparator *user_comparator_;
    public:
        explicit InternalKeyComparator(const Comparator *c) : user_comparator_(c) {}
        const char *Name() const override;
        int Compare(const Slice &a, const Slice &b) const override;
        void FindShortestSeparator(std::string *start, const Slice &limit) const override;
        void FindShortSuccessor(std::string *key) const override;

        const Comparator *user_comparator() const { return user_comparator_; }

        int Compare(const InternalKey &a, const InternalKey &b) const;
}

int InternalKeyComparator::Compare(const InternalKey &a, const InternalKey &b) const
{
    return Compare(a.Encode(), b.Encode());
}

int InternalKeyComparator::Compare(const Slice &akey, const Slice &bkey) const
{
    int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
    if (r == 0)
    {
        const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8);
        const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8);
        if (anum > bnum)
        {
            r = -1;
        }
        else if (anum < bnum)
        {
            r = +1;
        }
    }
    return r;
}

KeyComparator 是对InternalKeyComparator的包装, 负责从 MemTable 中保存的 key 中提取出 internal_key(user_key + seq(7字节) + type(1字节)),最终comparator_.comparator.user_comparator()->Compare(Slice(key_ptr, key_length - 8), key.user_key()调用的方法是InternalKeyComparator::Compare

整体的排序规则如下:

  1. 优先按照 user key 进行排序。
  2. user key 相同的按照 seq 降序排序。
  3. user key 和 seq 相同的按照 type 降序排序(逻辑上不会达到这一步,因为一个 LevelDB 的 sequence 是单调递增的)。

所以,在一个 MemTable 中,相同的 user key 的多个版本,新的排在前面,旧的排在后面。

相关文章

网友评论

      本文标题:LevelDB MemTable

      本文链接:https://www.haomeiwen.com/subject/prvqyktx.html