leveldb memtable

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

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

view plain

  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中没有数据的“实际”删除,这里并没有实现删除方法。

初始化函数定义如下:

view plain

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

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

view plain

  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(其中并没有,这里仅仅是为了区分)。

查询的操作代码如下:

view plain

  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的起到桥梁的作用,定义如下:

view plain

  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

时间: 2024-08-12 23:31:01

leveldb memtable的相关文章

tag

zookeeper snapshot&txnlog client&server watcher quorum client details leader follower observer server failover leveldb memtable/disktable version/redo log filter/metaindex reader/writer compact bridview redis memcached localcache hbase hadoop stor

leveldb源码分析--Memtable

本节讲述内存中LevelDB的数据结构Memtable,Memtable义如其名即为内存中的KV Table,即LSM-Tree中的C0 Tree.我们知道在LSM-Tree中刚插入的的KV数据都是存储在内存中,当内存中存储的数据超过一定量以后再写到磁盘中.而对于leveldb来说这个过程演变为内存中的数据都是插入到MemTable中,当MemTable中的数据超过一定量(Options.write_buffer_size)以后MemTable就转化为Immutable Memtable等待du

LevelDB源码之二MemTable

MemTable是内存表,在LevelDB中,内存表共两份,分别为: MemTable* mem_; MemTable* imm_; // Memtable being compacted 按数据的新旧程度,顺序依次为mem_,imm_,level0,level1,...... 关于LevelDB的结构,原理请参见http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html MemTable内部使用了前面介绍的SkipList做为数据

LevelDb日知录之五:MemTable详解

[LevelDb日知录之五:MemTable详解] LevelDb日知录前述小节大致讲述了磁盘文件相关的重要静态结构,本小节讲述内存中的数据结构Memtable,Memtable在整个体系中的重要地位也不言而喻.总体而言,所有KV数据都是存储在Memtable,Immutable Memtable和SSTable中的,Immutable Memtable从结构上讲和Memtable是完全一样的,区别仅仅在于其是只读的,不允许写入操作,而Memtable则是允许写入和读取的.当Memtable写入

深入leveldb-初步认识leveldb

文章参考http://blog.chinaunix.net/uid-26575352-id-3245476.html 1. leveldb简介 leveldb是google两位工程师实现的单机版k-v存储系统,具有以下几个特点 1. key和value都是任意的字节数组,支持内存和持久化存储 2. 数据都是按照key排序 3. 用户可以重写排序函数 4. 包含基本的数据操作接口,Put(key,value),Get(key),Delete(key) 5. 多操作可以当成一次原子操作 6. 用户可

leveldb - 并发写入处理

在并发写入的时候,leveldb巧妙地利用一个时间窗口做batch写入,这部分代码值得一读: Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { // A begin Writer w(&mutex_); w.batch = my_batch; w.sync = options.sync; w.done = false; // A end // B begin MutexLock l(&

LevelDB Cache实现机制分析

几天前淘宝量子恒道在博客上分析了HBase的Cache机制,本篇文章,结合LevelDB 1.7.0版本的源码,分析下LevelDB的Cache机制. 概述 LevelDB是Google开源的持久化KV单机存储引擎,据称是HBase的鼻祖Bigtable的重要组件tablet的开源实现.针对存储面对的普遍随机IO问题,LevelDB采用merge-dump的方式,将逻辑场景的随机写请求转换成顺序写log和写memtable的操作,由后台线程根据策略将memtable持久化成分层的sstable.

LevelDB Compaction操作

[LevelDB Compaction操作] 对于LevelDb来说,写入记录操作很简单,删除记录仅仅写入一个删除标记就算完事,但是读取记录比较复杂,需要在内存以及各个层级文件中依照新鲜程度依次查找,代价很高.为了加快读取速度,levelDb采取了compaction的方式来对已有的记录进行整理压缩,通过这种方式,来删除掉一些不再有效的KV数据,减小数据规模,减少文件数量等. levelDb的compaction机制和过程与Bigtable所讲述的是基本一致的,Bigtable中讲到三种类型的c

LevelDB 整体架构

[LevelDB 整体架构]     从图中可以看出,构成LevelDb静态结构的包括六个主要部分:内存中的MemTable和Immutable MemTable以及磁盘上的几种主要文件:Current文件,Manifest文件,log文件以及SSTable文件.当然,LevelDb除了这六个主要部分还有一些辅助的文件,但是以上六个文件和数据结构是LevelDb的主体构成元素. LevelDb的Log文件和Memtable与Bigtable论文中介绍的是一致的,当应用写入一条Key:Value记