memtable常驻于内存,需要按照key进行排序,通常意义上的话,可以使用二叉查找树来实现,跟进一步可以使用红黑树保证树的平衡,但是leveldb中使用了另外的一种数据结构:跳表Skip List。

memtable声明在db/memtable.h中,定义如下:

  1. class MemTable
  2. {
  3. public:
  4. // MemTables are reference counted.  The initial reference count
  5. // is zero and the caller must call Ref() at least once.
  6. explicit MemTable(const InternalKeyComparator& comparator);
  7. // Increase reference count.
  8. void Ref()
  9. {
  10. ++refs_;
  11. }
  12. // Drop reference count.  Delete if no more references exist.
  13. void Unref()
  14. {
  15. --refs_;
  16. assert(refs_ >= 0);
  17. if (refs_ <= 0)     // 如果引用数为0,删除该对象
  18. {
  19. delete this;
  20. }
  21. }
  22. // Returns an estimate of the number of bytes of data in use by this
  23. // data structure.
  24. //
  25. // REQUIRES: external synchronization to prevent simultaneous
  26. // operations on the same MemTable.
  27. // 返回使用的内存量
  28. size_t ApproximateMemoryUsage();
  29. // Return an iterator that yields the contents of the memtable.
  30. //
  31. // The caller must ensure that the underlying MemTable remains live
  32. // while the returned iterator is live.  The keys returned by this
  33. // iterator are internal keys encoded by AppendInternalKey in the
  34. // db/format.{h,cc} module.
  35. // 返回迭代器,遍历该memtable
  36. Iterator* NewIterator();
  37. // Add an entry into memtable that maps key to value at the
  38. // specified sequence number and with the specified type.
  39. // Typically value will be empty if type==kTypeDeletion.
  40. // 由于采用了LSM结构,所以没有数据删除,只有数据的添加,如果type=
  41. // kTypeDeletion,这时value为空
  42. // sequence number:递增的序列,用于数据的恢复
  43. void Add(SequenceNumber seq, ValueType type,
  44. const Slice& key,
  45. const Slice& value);
  46. // If memtable contains a value for key, store it in *value and return true.
  47. // If memtable contains a deletion for key, store a NotFound() error
  48. // in *status and return true.
  49. // Else, return false.
  50. // 查询该memtable
  51. bool Get(const LookupKey& key, std::string* value, Status* s);
  52. private:
  53. // 私有化析构函数,这样保证只有Unref()方法能够删除该对象
  54. ~MemTable();  // Private since only Unref() should be used to delete it
  55. struct KeyComparator
  56. {
  57. const InternalKeyComparator comparator;
  58. explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) { }
  59. int operator()(const char* a, const char* b) const;
  60. };
  61. // 迭代器
  62. friend class MemTableIterator;
  63. friend class MemTableBackwardIterator;
  64. // 跳表类型?作用
  65. typedef SkipList<const char*, KeyComparator> Table;
  66. // key比较器
  67. KeyComparator comparator_;
  68. // 对象被引用的数量,如果该值为0,则删除该对象
  69. int refs_;
  70. // 内存区域的封装
  71. Arena arena_;
  72. // 使用跳表数据结构保证内存数据按照key排序
  73. Table table_;
  74. // No copying allowed
  75. MemTable(const MemTable&);
  76. void operator=(const MemTable&);
  77. };

这里面有几个注意点,首先Arena对象实现了一套leveldb的内存管理策略,将在下面的文章中进行分析,这里仅仅将其想象成一个内存分配器即可;另外就是Table类型(实际上就是SkipList类型)也将在下面的文章中进行分析。下面主要关注memtable初始化、插入key、查询key,由于LSM模型中memtable中没有数据的“实际”删除,这里并没有实现删除方法。

初始化函数定义如下:

  1. MemTable::MemTable(const InternalKeyComparator& cmp)
  2. : comparator_(cmp),
  3. refs_(0),
  4. table_(comparator_, &arena_)
  5. {
  6. }

就是简单的完成refs_和table_的初始化。插入的函数定义如下:

  1. void MemTable:Add(SequenceNumber s, ValueType type,
  2. const Slice& key,
  3. const Slice& value)
  4. {
  5. // Format of an entry is concatenation of:
  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. // 首先格式化kv数据,之后分别写入,格式如下:
  11. // key_size, key_bytes, sequence_number|type(固定64位),
  12. // value_size,value
  13. size_t key_size = key.size();
  14. size_t val_size = value.size();
  15. size_t internal_key_size = key_size + 8;
  16. const size_t encoded_len =
  17. VarintLength(internal_key_size) + internal_key_size +
  18. VarintLength(val_size) + val_size;
  19. char* buf = arena_.Allocate(encoded_len);
  20. char* p = EncodeVarint32(buf, internal_key_size);
  21. memcpy(p, key.data(), key_size);
  22. p += key_size;
  23. EncodeFixed64(p, (s << 8) | type);  // (sequencenum << 8) | type
  24. p += 8;
  25. p = EncodeVarint32(p, val_size);
  26. memcpy(p, value.data(), val_size);
  27. assert((p + val_size) - buf == encoded_len);
  28. // 插入数据
  29. table_.Insert(buf);
  30. }

思路上相对比较简单,首先对用户传递进来的kv进行格式化,之后调用table_的Insert方法插入到数据库中,需要注意:1. 新出现了Slice类型,基本上和std::string类型向类似,代码比较简单,这里略过;2. 用户传递进来的kv最终被封装到了一个char数组中,格式为key_size, key_bytes, (sequence_number << 8)|type,value_size, value_bytes(其中并没有,这里仅仅是为了区分)。

查询的操作代码如下:

  1. bool MemTable::Get(const LookupKey& key, std::string* value, Status* s)
  2. {
  3. Slice memkey = key.memtable_key();
  4. Table::Iterator iter(&table_);
  5. // 查找key
  6. iter.Seek(memkey.data());
  7. if (iter.Valid())
  8. {
  9. // entry format is:
  10. //    klength  varint32
  11. //    userkey  char[klength]
  12. //    tag      uint64
  13. //    vlength  varint32
  14. //    value    char[vlength]
  15. // Check that it belongs to same user key.  We do not check the
  16. // sequence number since the Seek() call above should have skipped
  17. // all entries with overly large sequence numbers.
  18. const char* entry = iter.key();
  19. uint32_t key_length;
  20. const char* key_ptr = GetVarint32Ptr(entry, entry+5, &key_length);
  21. if (comparator_.comparator.user_comparator()->Compare(
  22. Slice(key_ptr, key_length - 8),
  23. key.user_key()) == 0)   // memtable找到的key和用户查找的key相同
  24. {
  25. // Correct user key
  26. const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
  27. switch (static_cast<ValueType>(tag & 0xff)) // 屏蔽低8位
  28. {
  29. case kTypeValue:    // 如果是kTypeValue类型,表明找到
  30. {
  31. Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
  32. value->assign(v.data(), v.size());
  33. return true;
  34. }
  35. case kTypeDeletion: // 如果是kTypeDeletion删除的数据
  36. *s = Status::NotFound(Slice());
  37. return true;
  38. }
  39. }
  40. }
  41. return false;
  42. }

Get函数内首先通过Table查找key,如果找到该key,解析key的内容,如果是kTypeValue类型的,返回给客户端查找到的value,如果是kTypeDeletion类型的(参考leveldb源代码分析:理论基础),返回给客户端表明没有找到。这里需要注意的是Get的参数中使用了LookupKey,该类型实际上就是在用户输入的key/value和leveldb内部使用key的起到桥梁的作用,定义如下:

  1. // A helper class useful for DBImpl::Get()
  2. class LookupKey{
  3. public:
  4. // Initialize *this for looking up user_key at a snapshot with
  5. // the specified sequence number.
  6. LookupKey(const Slice& user_key, SequenceNumber sequence);
  7. ~LookupKey();
  8. // Return a key suitable for lookup in a MemTable.
  9. Slice memtable_key() const { return Slice(start_, end_ - start_); }
  10. // Return an internal key (suitable for passing to an internal iterator)
  11. Slice internal_key() const { return Slice(kstart_, end_ - kstart_); }
  12. // Return the user key
  13. Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); }
  14. private:
  15. // We construct a char array of the form:
  16. //    klength  varint32               <-- start_
  17. //    userkey  char[klength]          <-- kstart_
  18. //    tag      uint64
  19. //                                    <-- end_
  20. // The array is a suitable MemTable key.
  21. // The suffix starting with "userkey" can be used as an InternalKey.
  22. const char* start_;
  23. const char* kstart_;
  24. const char* end_;
  25. char space_[200];      // Avoid allocation for short keys
  26. // No copying allowed
  27. LookupKey(const LookupKey&);
  28. void operator=(const LookupKey&);
  29. };

至此基本上memtable的初始化、读取、插入的操作分析完了,未解决问题如下:

1. SkipList数据结构

2. Arena内存分配策略

leveldb memtable的更多相关文章

  1. LevelDB源码分析-MemTable

    MemTable(db/memtable.h db/memtable.cc db/skiplist.h) LevelDB中存储在内存中的那部分KV数据都存储在memtable中,而memtable中的 ...

  2. leveldb 学习记录(三) MemTable 与 Immutable Memtable

    前文: leveldb 学习记录(一) skiplist leveldb 学习记录(二) Slice 存储格式: leveldb数据在内存中以 Memtable存储(核心结构是skiplist 已介绍 ...

  3. LevelDb日知录之五:MemTable详解

    [LevelDb日知录之五:MemTable详解] LevelDb日知录前述小节大致讲述了磁盘文件相关的重要静态结构,本小节讲述内存中的数据结构Memtable,Memtable在整个体系中的重要地位 ...

  4. LevelDB学习笔记 (3): 长文解析memtable、跳表和内存池Arena

    LevelDB学习笔记 (3): 长文解析memtable.跳表和内存池Arena 1. MemTable的基本信息 我们前面说过leveldb的所有数据都会先写入memtable中,在leveldb ...

  5. leveldb源码分析--SSTable之Compaction

    对于compaction是leveldb中体量最大的一部分,也应该是最为复杂的部分,为了便于理解我们首先从一些基本的概念开始.下面是一些从doc/impl.html中翻译和整理的内容: Level 0 ...

  6. leveldb 学习笔记之VarInt

    在leveldb在查找比较时的key里面保存key长度用的是VarInt,何为VarInt呢,就是变长的整数,每7bit代表一个数,第8bit代表是否还有下一个字节, 1. 比如小于128(一个字节以 ...

  7. LevelDB库简介

    LevelDB库简介 一.LevelDB入门 LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询 ...

  8. leveldb - log格式

    log文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及D ...

  9. leveldb - 并发写入处理

    在并发写入的时候,leveldb巧妙地利用一个时间窗口做batch写入,这部分代码值得一读: Status DBImpl::Write(const WriteOptions& options, ...

随机推荐

  1. 牛客练习赛33 B tokitsukaze and RPG (类埃筛)

    链接:https://ac.nowcoder.com/acm/contest/308/B 来源:牛客网 tokitsukaze and RPG 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/ ...

  2. 安装win10笔记

    1.使用pe安装的时候,要利用winNTSetup安装 2. 3.引导和安装驱动器都选择c盘 4.版本选择教育版,专业版photoshop 不好使.

  3. 获取sender进程所产生的trace文件

    直接开启数据库实例级别的sql_trace是不现实的,会对所有连接到oracle的session都产生sql_trace,这样会产生大量的trace文件(垃圾文件),因此为跟踪特定进程的sql信息,可 ...

  4. git push -u origin master和git push <远程主机名> <本地分支名>:<远程分支名>作用

    git push git push命令用于将本地分支的更新,推送到远程主机.它的格式与git pull命令相仿. $ git push <远程主机名> <本地分支名>:< ...

  5. postman实现Base64加密

    1.新建一个Collection 2.新建一个request 3.新增一个环境变量(全局变量也可以) 4.在variable中填入需要加密的变量名称,比如password 5.在body中填好参数,需 ...

  6. 【Luogu4299】首都

    BZOJ权限题. 洛谷 题目描述 在X星球上有N个国家,每个国家占据着X星球的一座城市.由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的. X星球上战乱频发,如果A国打败了B国,那么B ...

  7. kafka完全分布式搭建(2.12版)

    1.下载解压 tar -xvf kafka_2.12-1.0.0.tgz 2.进入config目录下,修改server.propeties文件 broker.id=0 #当前server编号 port ...

  8. facebook第三方登陆实践

    未完,待续... 1.注册 到Facebook官网注册开发者账号,创建应用(开发者平台 https://developers.facebook.com),如果尚未注册账号的请注册账号并进行登录) 注册 ...

  9. FMDB源码解析(上)-FMDB基本使用

    目录 一: 初识FMDB 二: 基本使用 三: 基本操作 结束 最后更新:2017-02-22 2017, 说到做到 一: 初识FMDB FMDB是iOS平台的SQLite数据库框架 FMDB以OC的 ...

  10. 获取项目配置的常用方法(Struts/Servlet)

    struts: //web.xml中: <context-param> <param-name>paramName</param-name> <param-v ...