• 技术贴 | Rocksdb 中 Memtable 源码解析


     

    一、什么是 Memtable?

    Memtable 是 Rocksdb 在内存中保存数据的一种数据结构,一个 Memtable 的容量是固定的,在 Memtable 写满后,会转换为  Immutable Memtable,Immutable Memtable 中的数据会 Flush 到 SST File 中。

    Memtable 和 Immutable Memtable 的唯一区别是 Memtable 可读可写,而 Immutable Memtable 是只读且不允许写入。Rocksdb 引入了 Column Family 的概念,在一个 Column Family 中只有一个 Memtable,但允许存在多个 Immutable Memtable。Rocksdb 支持创建多数据结构类型的 Memtable,默认的是 SkipList,即跳跃表。

    二、Memtable 的数据结构

    Rocksdb 中 Memtable 有多种实现方式 (SkipList / HashSkipList / HashLinkList / Vector),其中默认的实现方式为 SkipList。

    一个 Memtable 中维护了两个 SkipList,其中范围删除插入 range_del_table_,其余的操作写入 table_。

    Memtable 定义的操作接口 Add () 如下:

    1. bool MemTable::Add(SequenceNumber s, ValueType type,
    2. const Slice& key, /* user key */
    3. const Slice& value, bool allow_concurrent,
    4. MemTablePostProcessInfo* post_process_info) {
    5. // 一条key-value Entry的数据格式
    6. // key_size : varint32 of internal_key.size()
    7. // key bytes : char[internal_key.size()]
    8. // value_size : varint32 of value.size()
    9. // value bytes : char[value.size()]
    10. uint32_t key_size = static_cast<uint32_t>(key.size());
    11. uint32_t val_size = static_cast<uint32_t>(value.size());
    12. uint32_t internal_key_size = key_size + 8;
    13. const uint32_t encoded_len = VarintLength(internal_key_size) +
    14. internal_key_size + VarintLength(val_size) +
    15. val_size;
    16. char* buf = nullptr;
    17. // 通过判断key-value的类型来选择memtable, 范围删除的kv插入range_del_table_
    18. std::unique_ptr<MemTableRep>& table =
    19. type == kTypeRangeDeletion ? range_del_table_ : table_;
    20. KeyHandle handle = table->Allocate(encoded_len, &buf);
    21. //...
    22. // 是否允许并发插入
    23. if (!allow_concurrent) {
    24. // 是否制定了函数提取key的前缀
    25. if (insert_with_hint_prefix_extractor_ != nullptr &&
    26. insert_with_hint_prefix_extractor_->InDomain(key_slice)) {
    27. // ...
    28. bool res = table->InsertWithHint(handle, &insert_hints_[prefix]);
    29. } else {
    30. // 插入key-value pair
    31. bool res = table->Insert(handle);
    32. if (UNLIKELY(!res)) {
    33. return res;
    34. }
    35. }
    36. } else {
    37. // 插入key-value pair
    38. bool res = table->InsertConcurrently(handle);
    39. if (UNLIKELY(!res)) {
    40. return res;
    41. }
    42. }
    43. return true;
    44. }

    Add () 函数将用户的 key 和 value 封装成一个 buf,然后根据不同的条件调用 table->Insert () 插入至 Memtable。table 就是 Memtable 的工厂类实现,默认 SkiplistRep, 即通过调用 SkipList 的 Insert () 完成 key 的插入。

    Memtable 定义的操作接口  Get () 如下:

    1. bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
    2. MergeContext* merge_context,
    3. RangeDelAggregator* range_del_agg, SequenceNumber* seq,
    4. const ReadOptions& read_opts, ReadCallback* callback,
    5. bool* is_blob_index) {
    6. // 在range_del_table_上初始化一个迭代器
    7. std::unique_ptr<InternalIterator> range_del_iter(
    8. NewRangeTombstoneIterator(read_opts));
    9. Status status = range_del_agg->AddTombstones(std::move(range_del_iter));
    10. if (!status.ok()) {
    11. *s = status;
    12. return false;
    13. }
    14. Slice user_key = key.user_key();
    15. // 利用前缀提取过滤判断key是否存在
    16. bool const may_contain =
    17. nullptr == prefix_bloom_

    Memtable 的 Get () 调用了 SkipListRep 的 Get () 接口,最终是通过 SkipList 的 FindGreaterOrEqual () 来查找。查找出来的 key 会被传入的回调函数 SaveValu 并 e () 根据 type 处理,例如 ktypeDeletion 就返回 NotFound ()。

    三、什么是 SkipList?

    SkipList 即跳跃表,在普通单向链表的基础上增加了一些索引,而且这些索引是分层的,从而可以快速地查到数据。如下是一个典型的跳跃表构建过程:

    初始我们有个带头结点的有序链表 a,而后每相邻两个节点增加一个指针,让指针指向下下个节点,得到表 b。这样所有新增指针连成了一个新的链表,但它包含的节点个数只有原来的一半。其后我们对第二层链表再次进行此操作,得到表 c。重复这个过程,直到采样出的节点只剩一个,如图 d。这样便完成了跳跃表的构建。跳跃表查找过程如下:

    从 head 开始,head 的 level 为 4,判断 head 后继节点值小于 <12,此时当前节点变为 6,继续查找;节点 6 的 level 为 3,判断后继节点值为 NIL,因此 level 降低到 2;判断 x -> forward [2] -> key (25) > 17,继续降低 level 到 1;判断 x -> forward [1] -> key (9) < 17,此时 x 变为 x ->forward [1],x 成为节点 9;节点 9 的判断 x -> forward [1] -> key 为 17,因此找到节点,直接返回。

    跳跃表插入过程如下:

    我们以上图为例,list -> leve=4,如果要插入节点 17,首先确定搜索路径,与之前步骤类似。

    创建新节点 Node (17),并为其生成 level (随机),该 level 可能值为 [1, MaxLevel],此时需要对比,如果 level < list -> level,需要先将突出部分从 header 指向它,这里新生成的节点 Node (17) 的 level  为 5,超过了 list 当前的最大 level,于是将 update [4] 设置为 header,后续直接将 Node (17) 作为 header 的后继。

    最后是设置搜索路径上每个节点的后继关系,这样我们便完成了节点的插入。我们来看一下 SkipList 的具体代码实现:

    InlineSkipList 数据结构 >>

    1. class InlineSkipList {
    2. private:
    3. struct Node;
    4. struct Splice;
    5. public:
    6. using DecodedKey = \
    7. typename std::remove_reference<Comparator>::type::DecodedType;
    8. Allocator* const allocator_;
    9. Comparator const compare_;
    10. Node* const head_;
    11. std::atomic<int> max_height_;
    12. Splice* seq_splice_;
    13. };

    Node 的数据结构 >>

    1. template <class Comparator>
    2. struct InlineSkipList<Comparator>::Node {
    3. private:
    4. // 存放该节点的next_节点的数组
    5. // 数组大小为该节点的height,当调用NewNode()分配内存初始化整个数组
    6. std::atomic<Node*> next_[1];
    7. };

    Node 的数据结构如图,它将 key 和链表每层的指针连续存储,通过 next_[-n] 这种方式来访问每层的 next 指针,此外在 new 新节点时会把该节点高度写在 next_[0] 的前 4 个字节处,当完成插入后,next_[0] 会恢复成指向同层的下一个节点的指针。

    四、InlineSkipList 插入

    Memtable 的 Add () 通过 SkipList 的 Insert () 来查找,下面是 Insert () 的具体实现:

    1. bool InlineSkipList<Comparator>::Insert(const char* key, Splice* splice,
    2. bool allow_partial_splice_fix) {
    3. Node* x = reinterpret_cast(const_cast(key)) - 1; // x即为next_[0]
    4. const DecodedKey key_decoded = compare_.decode_key(key);
    5. int height = x->UnstashHeight();
    6. assert(height >= 1 && height <= kMaxHeight_);
    7. int max_height = max_height_.load(std::memory_order_relaxed);
    8. // 更新max_height
    9. while (height > max_height) {
    10. if (max_height_.compare_exchange_weak(max_height, height)) {
    11. // successfully updated it
    12. max_height = height;
    13. break;
    14. }
    15. // 否则重试,可能因为其他人增加了它而退出循环
    16. }
    17. assert(max_height <= kMaxPossibleHeight);
    18. // 插入节点的时候,需要借助一个Splice对象,该对象主要保存着最近一次插入的节点快照
    19. // 它保存着一个prev和next的节点指针数组,由Level可以索引到对应Level的节点
    20. int recompute_height = 0;
    21. if (splice->height_ < max_height) {
    22. // 当重置splice
    23. splice->prev_[max_height] = head_;
    24. splice->next_[max_height] = nullptr;
    25. splice->height_ = max_height;
    26. recompute_height = max_height;
    27. } else {
    28. while (recompute_height < max_height) {
    29. if (splice->prev_[recompute_height]->Next(recompute_height) !=
    30. splice->next_[recompute_height]) { //判断该层的splice是否紧密,即prev_->Next是否等于next_
    31. ++recompute_height;
    32. } else if (splice->prev_[recompute_height] != head_ &&
    33. !KeyIsAfterNode(key_decoded,
    34. splice->prev_[recompute_height])) { //小于splice当前层的prev_
    35. // ...
    36. } else if (KeyIsAfterNode(key_decoded,
    37. splice->next_[recompute_height])) { //大于splice当前层的prev_
    38. // ...
    39. } else {
    40. // 找到了合适的level
    41. break;
    42. }
    43. }
    44. }
    45. assert(recompute_height <= max_height);
    46. if (recompute_height > 0) {//计算splice
    47. RecomputeSpliceLevels(key_decoded, splice, recompute_height); // 找到要插入的key合适的splice
    48. }
    49. bool splice_is_valid = true;
    50. if (UseCAS) {//CAS无锁机制
    51. //...
    52. } else {
    53. for (int i = 0; i < height; ++i) {
    54. if (i >= recompute_height &&
    55. splice->prev_[i]->Next(i) != splice->next_[i]) { // 确保splice此Level有效,如果无效的话再查找一次
    56. FindSpliceForLevel<false>(key_decoded, splice->prev_[i], nullptr, i,
    57. &splice->prev_[i], &splice->next_[i]);
    58. }
    59. // Checking for duplicate keys on the level 0 is sufficient
    60. if (UNLIKELY(i == 0 && splice->next_[i] != nullptr &&
    61. compare_(x->Key(), splice->next_[i]->Key()) >= 0)) {
    62. // duplicate key
    63. return false;
    64. }
    65. if (UNLIKELY(i == 0 && splice->prev_[i] != head_ &&
    66. compare_(splice->prev_[i]->Key(), x->Key()) >= 0)) {
    67. // duplicate key
    68. return false;
    69. }
    70. //
    71. x->NoBarrier_SetNext(i, splice->next_[i]); //将新节点next指向对应的next节点
    72. splice->prev_[i]->SetNext(i, x); //将splice的prev节点的next指向新节点
    73. }
    74. }
    75. if (splice_is_valid) {//将新节点Height下的prev节点都设置为该节点,因为原先的prev和next之间已经不连续了。
    76. for (int i = 0; i < height; ++i) {
    77. splice->prev_[i] = x;
    78. }
    79. //...
    80. } else {
    81. splice->height_ = 0;
    82. }
    83. return true;
    84. }

    五、InlineSkipList 查找

    Memtable 的 Get () 通过 SkipList 的 FindGreaterOrEqual () 来查找,下面是 FindGreaterOrEqual () 的具体实现:

    1. InlineSkipList<Comparator>::FindGreaterOrEqual(const char* key) const {
    2. Node* x = head_;
    3. int level = GetMaxHeight() - 1;//从最高层开始查找
    4. Node* last_bigger = nullptr;
    5. const DecodedKey key_decoded = compare_.decode_key(key);
    6. while (true) {
    7. Node* next = x->Next(level);
    8. if (next != nullptr) {
    9. PREFETCH(next->Next(level), 0, 1);
    10. }
    11. // Make sure the lists are sorted
    12. assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x));
    13. // Make sure we haven't overshot during our search
    14. assert(x == head_ || KeyIsAfterNode(key_decoded, x));
    15. int cmp = (next == nullptr || next == last_bigger)
    16. ? 1
    17. : compare_(next->Key(), key_decoded);
    18. if (cmp == 0 || (cmp > 0 && level == 0)) { // 找到相等的key或者查找的key不在此范围内
    19. return next;
    20. } else if (cmp < 0) { //待查找 key 比 next 大,则在该层继续查找
    21. x = next;
    22. } else { // 待查找 key 不大于 next,且没到底,则继续往下层查找
    23. // Switch to next list, reuse compare_() result
    24. last_bigger = next;
    25. level--;
    26. }
    27. }
    28. }
  • 相关阅读:
    MySQL5.7慢查询实践
    MySQL -- JDBC
    机器学习实战读书笔记——端到端的机器学习项目
    华为认证 | HCIA、HCIP、HCIE,难度区别在哪里?
    缓存-基础理论和Guava Cache介绍
    技术:如何设计zkVM电路
    一个网址包含有什么? URL-简介
    229. 多数元素 II Python
    Spring之事务实现原理及其注解@Transactional底层和传播机制原理
    python安全脚本开发简单思路
  • 原文地址:https://blog.csdn.net/ZNBase/article/details/127886018