LevelDB memtable结构

levelDB数据库中,其中有两部分在内存中保存,memtable和immutable结构。两者使用的其实都是基于类MemTable申请的内存。下面就详细介绍一下Memtable类。

MemTable 和 SkipList 之间有这千丝万缕的联系,MemTable实则是对SkipList进行了封装。
SkipList的声明是一个类模板,他的实例化是在MemTable类声明中进行的,LevelDB中memtable块的底层实现是由SkipList完成的。

SkipList和MemTable都有其对应的迭代器。MemTable迭代器实则是对SkipList::Iterator的进一步封装。
在这里插入图片描述

基础函数

const char* GetVarint32Ptr(const char* p, const char* limit, uint32_t* v)

解析levelDB 中Varint32 类型的数据,在levelDB中数据一般先存储数据大小,然后存储真实的数据。
p :Varint32数据的起始地址;
limit : Varint32数据最多用5位,所以limit 为p+5;
v :数据的长度;
return 返回真实数据的起始地址

GetLengthPrefixedSlice函数

hh在这里插入图片描述

static Slice GetLengthPrefixedSlice(const char* data) {
  uint32_t len;
  const char* p = data;
  p = GetVarint32Ptr(p, p + 5, &len);  // +5: we assume "p" is not corrupted
  return Slice(p, len);
}

EncodeFixed64函数

将uint64类型按照char类型进行存储

inline void EncodeFixed64(char* dst, uint64_t value) {
  uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);

  // Recent clang and gcc optimize this to a single mov / str instruction.
  buffer[0] = static_cast<uint8_t>(value);
  buffer[1] = static_cast<uint8_t>(value >> 8);
  buffer[2] = static_cast<uint8_t>(value >> 16);
  buffer[3] = static_cast<uint8_t>(value >> 24);
  buffer[4] = static_cast<uint8_t>(value >> 32);
  buffer[5] = static_cast<uint8_t>(value >> 40);
  buffer[6] = static_cast<uint8_t>(value >> 48);
  buffer[7] = static_cast<uint8_t>(value >> 56);
}

SkipList 结构

SkipList中每个node节点中key的结构如下,如下结构按照Slice类型存储到node中。
在这里插入图片描述
其中 :

internal_key_size 是levelDB内部Varint32类型,记录key.size()+8
value size 也是varint32类型,记录value.size()

MemTable

MemTable类是对SkipList类的封装,那么为什么封装呢?

  1. SkipList是一个类模板,是没有具体类型的。在MemTable类中,进行具体实例话,存储成成员对象;
  2. 在append数据的时候,输入数据是不符合SkipList 中节点结构的。需要用输入数据构造出node节点所存储的key结构。构造的过程就放在MemTable中。
  3. 在MemTable中构造比较器
  4. …(再思考)
class MemTable {
 public:
  explicit MemTable(const InternalKeyComparator& comparator);
  MemTable(const MemTable&) = delete;
  MemTable& operator=(const MemTable&) = delete;
  // Increase reference count.
  void Ref() { ++refs_; }
  // Drop reference count.  Delete if no more references exist.
  void Unref() {
    --refs_;
    assert(refs_ >= 0);
    if (refs_ <= 0) {
      delete this;
    }
  }
  // Returns an estimate of the number of bytes of data in use by this
  // data structure. It is safe to call when MemTable is being modified.
  size_t ApproximateMemoryUsage();
  Iterator* NewIterator();
  // 构造SkipList的Node节点的key内容,然后调用SkipList::Insert函数,实现添加操作
  void Add(SequenceNumber seq, ValueType type, const Slice& key,
           const Slice& value);
  bool Get(const LookupKey& key, std::string* value, Status* s);
 private:
  friend class MemTableIterator;
  friend class MemTableBackwardIterator;
  // MemTable内部维护一个比较器,可以直接比较SkipList结构中node节点
  struct KeyComparator {
    const InternalKeyComparator comparator;   // InternalKey 的比较器
    explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) {}
    int operator()(const char* a, const char* b) const;
  };
  typedef SkipList<const char*, KeyComparator> Table;
  ~MemTable();  // Private since only Unref() should be used to delete it
  KeyComparator comparator_;
  int refs_;
  Arena arena_;
  // MemTable类对SkipList进行封装,其实DB的memtable中的记录都存储在SkipList中。
  Table table_;
};

MemTable中有两个友元类:

friend class MemTableIterator;
friend class MemTableBackwardIterator;

其中MemTableIterator是MemTable的迭代器,那为什么将MemTableIterator 定义为MemTable的友元:

在MemTableIterator类中,定义了成员变量MemTable::Table::Iterator iter_, 其中 Table 是类型别名,定义在了MemTable的private中,所以需要将MemTableIterator定义为其友元类,才能访问。

其实MemTableIterator 是对SkipList的iterator的封装类,为了与MemTable类保持一致。

另外,在MemTable类中,最终要的两个成员函数就是:Add()和Get(),一个是向memtable中添加key-value,一个是获取指定key对应的最新value。

Add函数:
步骤:

  1. 构造插入到skiplist中的node节点
    1.1 计算node节点的内存大小
    1.2 申请内存
    1.3 逐步将 internal_key_size key value_size value 插入到新申请的内存中
  2. 调用skip list的insert接口插入node节点

MemTable::Get函数

获取如下结构的 key的值,结构为size + 对应的key。
在这里插入图片描述

bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
  Slice memkey = key.memtable_key();
  // 根据MemTable内部维护的SkipList,创建SkipList的迭代器
  Table::Iterator iter(&table_);
  // 找到第一个 >= memkey的节点
  iter.Seek(memkey.data());
  if (iter.Valid()) {
    // entry format is:
    //    klength  varint32
    //    userkey  char[klength]
    //    tag      uint64
    //    vlength  varint32
    //    value    char[vlength]
    // Check that it belongs to same user key.  We do not check the
    // sequence number since the Seek() call above should have skipped
    // all entries with overly large sequence numbers.
    const char* entry = iter.key();
    uint32_t key_length;
    // GetVarint32Ptr结果:
    // key_ptr 指向key的起始位置,key_length大小为 = key.size() + 8,即internal_key_size;
    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) {
      // Correct user key
      const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
      switch (static_cast<ValueType>(tag & 0xff)) {
        case kTypeValue: {
          // 获取value
          Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
          value->assign(v.data(), v.size());
          return true;
        }
        case kTypeDeletion:
          *s = Status::NotFound(Slice());
          return true;
      }
    }
  }
  return false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LevelDB 中,不可变 memtable(immutable memtable)的引入有以下几个原因: 1. 提高写入性能:LevelDB 使用了一种称为 Log-Structured Merge-Tree(LSM-Tree)的存储结构,其中包含了多个层级的数据文件。当写入操作发生时,数据首先会被写入 memtable,然后再根据一定的策略将 memtable 转化为不可变的 SST(Sorted String Table)文件。这样一来,写入操作就可以快速完成,因为不需要进行磁盘上的随机写入操作。而不可变 memtable 的引入,则是为了将写入操作转化为创建新的数据结构,避免频繁的数据移动和复制,进一步提高写入性能。 2. 提高读取性能:不可变 memtable 的存在使得读取操作可以并发地进行,而不需要等待写入操作完成。因为在不可变 memtable 创建期间,旧的 memtable 仍然可以用于读取操作。这种方式有效地解耦了读取和写入操作,提高了系统的并发性能。 3. 简化持久化:LevelDB 使用了基于日志的持久化机制,即将写入操作以日志的形式追加到磁盘上的文件中。当 memtable 转化为不可变的 SST 文件后,可以将其直接持久化到磁盘上,而不需要对其进行额外的复制和移动操作。这样可以简化持久化过程,提高系统的效率和可靠性。 因此,不可变 memtableLevelDB 中的引入是为了提高写入性能、并发读取操作以及简化持久化过程。它是 LevelDB 数据库引擎的重要组成部分,对于提高整体性能和效率非常关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值