4 Memtable
先写点最精妙的部分:
我们在插入一条k/v记录时是把k和v整合在一起当成一个新的key插入到跳表里,因为我们的跳表里只有key没有value。
查询(get)我们传入的仅仅是k,但跳表里的comparator是我们自己指定的,所以comparator会比较跳表中node里key的k部分和我们传入的k作比较,如果符合就把key的v部分返回。
4.6 Comparator
弄清楚了key,接下来就要看看key的使用了,先从Comparator开始分析。首先Comparator是一个抽象类,导出了几个接口。
其中Name()和Compare()接口都很明了,另外的两个Find xxx接口都有什么功能呢,直接看程序注释:
-
-
-
-
-
-
-
-
-
-
-
- virtual void FindShortestSeparator(std::string* start, const Slice& limit) const = 0;
-
-
-
-
-
-
- virtual void FindShortSuccessor(std::string* key) const = 0;
其中的实现类有两个,一个是内置的BytewiseComparatorImpl,另一个是InternalKeyComparator。下面分别来分析。
4.6.1 BytewiseComparatorImpl
首先是重载的Name和比较函数,比较函数如其名,就是字符串比较,如下:
virtual const char* Name() const {return"leveldb.BytewiseComparator";}
virtual int Compare(const Slice& a, const Slice& b) const {return a.compare(b);}
再来看看Byte wise的comparator是如何实现FindShortestSeparator()的,没什么特别的,代码+注释如下:
- virtual void FindShortestSeparator(std::string* start, const Slice& limit) const
- {
-
- size_t min_length = std::min(start->size(), limit.size());
- size_t diff_index = 0;
- while ((diff_index < min_length) && ((*start)[diff_index] == limit[diff_index])) {
- diff_index++;
- }
- if (diff_index >= min_length) {
-
- } else {
-
-
- uint8_t diff_byte = static_cast<uint8_t>((*start)[diff_index]);
- if (diff_byte < static_cast<uint8_t>(0xff) &&
- diff_byte + 1 < static_cast<uint8_t>(limit[diff_index])) {
- (*start)[diff_index]++;
- start->resize(diff_index + 1);
- assert(Compare(*start, limit) < 0);
- }
- }
- }
最后是FindShortSuccessor(),这个更简单了,代码+注释如下:
- virtual void FindShortSuccessor(std::string* key) const
- {
-
-
- size_t n = key->size();
- for (size_t i = 0; i < n; i++) {
- const uint8_t byte = (*key)[i];
- if (byte != static_cast<uint8_t>(0xff)) {
- (*key)[i] = byte + 1;
- key->resize(i+1);
- return;
- }
- }
- }
Leveldb内建的基于Byte wise的comparator类就这么多内容了,下面再来看看InternalKeyComparator。
4.6.2 InternalKeyComparator
从上面对Internal Key的讨论可知,由于它是由user key和sequence number和value type组合而成的,因此它还需要user key的比较,所以InternalKeyComparator有一个Comparator* user_comparator_成员,用于user key的比较。
在leveldb中的名字为:"leveldb.InternalKeyComparator",下面来看看比较函数:
Compare(const Slice& akey, const Slice& bkey),代码很简单,其比较逻辑是:
S1 首先比较user key,基于用户设置的comparator,如果user key不相等就直接返回比较结果;否则执行进入S2;
S2 取出8字节的sequence number | value type,如果akey的 > bkey的则返回-1,如果akey的<bkey的返回1,相等返回0;
由此可见其排序比较依据依次是:
>1 首先根据user key按升序排列
>2 然后根据sequence number按降序排列
>3 最后根据value type按降序排列
虽然比较时value type并不重要,因为sequence number是唯一的,但是直接取出8byte的sequence number | value type,然后做比较更方便,不需要再次移位提取出7byte的sequence number,又何乐而不为呢。这也是把value type安排在低7byte的好处吧,排序的两个依据就是user key和sequence number。
接下来就该看看其FindShortestSeparator()函数实现了,该函数取出Internal Key中的user key字段,根据user指定的comparator找到并替换start,如果start被替换了,就用新的start更新Internal Key,并使用最大的sequence number。否则保持不变。
函数声明:
void InternalKeyComparator::FindShortestSeparator(std::string* start, const Slice& limit) const;
函数实现:
-
- Slice user_start = ExtractUserKey(*start);
- Slice user_limit = ExtractUserKey(limit);
- std::string tmp(user_start.data(), user_start.size());
- user_comparator_->FindShortestSeparator(&tmp, user_limit);
- if(tmp.size()<user_start.size()&&user_comparator_->Compare(user_start, tmp)<0) {
-
-
- PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));
- assert(this->Compare(*start, tmp) < 0);
- assert(this->Compare(tmp, limit) < 0);
- start->swap(tmp);
- }
接下来是FindShortSuccessor(std::string* key)函数,该函数取出Internal Key中的user key字段,根据user指定的comparator找到并替换key,如果key被替换了,就用新的key更新Internal Key,并使用最大的sequence number。否则保持不变。实现逻辑如下:
- Slice user_key = ExtractUserKey(*key);
-
- std::string tmp(user_key.data(), user_key.size());
- user_comparator_->FindShortSuccessor(&tmp);
- if(tmp.size()<user_key.size() && user_comparator_->Compare(user_key, tmp)<0) {
-
-
- PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));
- assert(this->Compare(*key, tmp) < 0);
- key->swap(tmp);
- }
4.7 Memtable::Insert()
把相关的Key和Key Comparator都弄清楚后,是时候分析memtable本身了。首先是向memtable插入记录的接口,函数原型如下:
void Add(SequenceNumber seq, ValueType type, const Slice& key, const Slice& value);
代码实现如下:
-
-
-
-
-
- size_t key_size = key.size();
- size_t val_size = value.size();
- size_t internal_key_size = key_size + 8;
- 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);
- memcpy(p, key.data(), key_size);
- p += key_size;
- EncodeFixed64(p, (s << 8) | type);
- p += 8;
- p = EncodeVarint32(p, val_size);
- memcpy(p, value.data(), val_size);
- assert((p + val_size) - buf == encoded_len);
- table_.Insert(buf);
根据代码,我们可以分析出KV记录在skip list的存储格式等信息,首先总长度为:
VarInt(Internal Key size) len + internal key size + VarInt(value) len + value size。它们的相互衔接也就是KV的存储格式:
| VarInt(Internal Key size) len | internal key |VarInt(value) len |value|
其中前面说过:
internal key = |user key |sequence number |type |
Internal key size = key size + 8
Memtable的查找接口,根据一个LookupKey找到响应的记录,函数声明:
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()) {
-
-
- const char* entry = iter.key();
- uint32_t key_length;
- 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) {
-
- 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;
这段代码,主要就是一个Seek函数,根据传入的LookupKey得到在memtable中存储的key,然后调用Skip list::Iterator的Seek函数查找。Seek直接调用Skip list的FindGreaterOrEqual(key)接口,返回大于等于key的Iterator。然后取出user key判断时候和传入的user key相同,如果相同则取出value,如果记录的Value Type为kTypeDeletion,返回Status::NotFound(Slice())。
4.9 小结
Memtable到此就分析完毕了,本质上就是一个有序的
Skip list,排序基于
user key的
sequence number,其排序比较依据依次是:
>1首先根据user key按升序排列
>2然后根据sequence number按降序排列
>3最后根据value type按降序排列(这个其实无关紧要)