levelDB浅谈2--MemTable

233 阅读2分钟

MemTable是LevelDB内存的类对象。用户在插入数据时,LevelDB会先将数据插入到Memtable中,MemTable会将数据插入到SkipList中。

class MemTable {
 public:
  explicit MemTable(const InternalKeyComparator& comparator);

  MemTable(const MemTable&) = delete;
  MemTable& operator=(const MemTable&) = delete;
  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();
  
  void Add(SequenceNumber seq, ValueType type, const Slice& key,
           const Slice& value);
  bool Get(const LookupKey& key, std::string* value, Status* s);

 private:
  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;

  ~MemTable();  // Private since only Unref() should be used to delete it

  KeyComparator comparator_;
  int refs_;
  Arena arena_;
  Table table_;
};

MemTable::MemTable(const InternalKeyComparator& comparator)
    : comparator_(comparator), refs_(0), table_(comparator_, &arena_) {}

其成员变量有:

  KeyComparator comparator_;  // 比较器,用于比较 MemTable Entry
                              // 该比较其内部维护一个InternalKeyComparator,实际比较InternalKey
  int refs_;     // 计数器,即有多少线程该访问该MemTable
  Arena arena_;  // 内存申请器,在类内进初始化,然后在创建SkipList时,传过去
  Table table_;  // MemTable对象内,维护的SkipList

MemTable 实现过程中,有一个关于内存申请有一个细节,注意成员数据 arena_, 在MemTable初始化对象时,arena_ 是在类内初始化。在 MemTable 构造函数中,初始化成员数据 table_ 时,传入了 &arena_。也就是说 MemTable 和其底层结构 SkipList 共用一个内存申请器

另外一个在LevelDB代码阅读,容易让人混乱的是 key 在各个环节中的存在形式。

在讲解MemTable对外接口之前,先了解两个重要结构。让我们仔细想想,作为用户插入的是一个key-value值,那么数据在 MemTable 中如何表示?SkipList 作为 Memtable 的底层存储结构,数据在SkipList中又如何表示呢?

InternalKey

MemTable不会对已经存储的数据进行delete操作,那么就需要一个sequence_id表示key的先后顺序,并且单独有一个字段标志该key是否已经删除,所以相当于对原始用户输入的key进行扩展,即 InternalKey 结构。

image.png

void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
  result->append(key.user_key.data(), key.user_key.size());
  PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
}
struct ParsedInternalKey {
  Slice user_key;
  SequenceNumber sequence;
  ValueType type;
};

从接口 AppendInternalKey中,可以清晰看到 InternalKey 是如何构造的。其中Sequence就是全局维护的id,表示key先后顺序。type表示该key是否为删除状态:0(删除)或1。

LookupKey

结构 LookupKey,又称 memtable_key。细心的同学已经看出,该结构体即为 MemTable::Get 接口的输入。LookupKey InternalKey 又进行了扩展,加入 InternalKey 的长度信息。 LookupKey 是 key 的终极形态。

image.png

MemTable_entry

MemTable_entry 是用户数据在 SkipList 中的结构。 image.png

MemTable::Add & MemTable::Get接口

MemTable::Add

/**
Memtable 插入数据
**/
void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
                   const Slice& value) {
  // Format of an entry is concatenation of:
  //  key_size     : varint32 of internal_key.size()
  //  key bytes    : char[internal_key.size()]
  //  value_size   : varint32 of value.size()
  //  value bytes  : char[value.size()]
  size_t key_size = key.size();     // key 字节个数
  size_t val_size = value.size();   // value 字节个数
  size_t internal_key_size = key_size + 8;  // internal_key_size 字节个数
  // MemTable_entry 所占字节
  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);
  // 填充 MemTable_entry 的内容
  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);     // 调用 SkipList 的Insert 函数
}

接口 Add 内容其实很好理解。唯一一个疑问是:为什么申请申请内存时,没有进行内存对齐。

MemTable::Get

bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
  Slice memkey = key.memtable_key();
  // 迭代器相关 的知识,之后再讲
  Table::Iterator iter(&table_);
  iter.Seek(memkey.data());
  if (iter.Valid()) {
    // entry format is:  entry 实则就是 MemTable_entry,数据在SkipList中的形式
    //    klength  varint32
    //    userkey  char[klength]
    //    tag      uint64
    //    vlength  varint32
    //    value    char[vlength]
    const char* entry = iter.key();
    uint32_t key_length;
    // key_ptr 指向key的起始位置,key_length大小为 = key.size() + 8,即internal_key_size;
    const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
    // 调用 user key 比较器
    if (comparator_.comparator.user_comparator()->Compare(
            Slice(key_ptr, key_length - 8), key.user_key()) == 0) {
      // key type
      const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
      switch (static_cast<ValueType>(tag & 0xff)) {
        case kTypeValue: {
          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;
}

参考

# leveldb 中的 varint 与 Key 组成