LevelDB源码剖析之Memtable(2)

来源:互联网 发布:幻萌网络微博 编辑:程序博客网 时间:2024/05/29 13:30

原文:http://blog.csdn.net/sparkliang/article/details/8604424


4 Memtable之2

4.6 Comparator

弄清楚了key,接下来就要看看key的使用了,先从Comparator开始分析。首先Comparator是一个抽象类,导出了几个接口。

其中Name()Compare()接口都很明了,另外的两个Find xxx接口都有什么功能呢,直接看程序注释:

[cpp] view plaincopy
  1. // Advanced functions: these are used to reduce the space requirements  
  2. // for internal data structures like index blocks.  
  3. // 这两个函数:用于减少像index blocks这样的内部数据结构占用的空间  
  4. // 其中的*start和*key参数都是IN OUT的。  
  5.   
  6. // If *start < limit, changes *start to a short string in [start,limit).  
  7. // Simple comparator implementations may return with *start unchanged,  
  8. // i.e., an implementation of this method that does nothing is correct.  
  9. // 这个函数的作用就是:如果*start < limit,就在[start,limit)中找到一个  
  10. // 短字符串,并赋给*start返回  
  11. // 简单的comparator实现可能不改变*start,这也是正确的  
  12. virtual void FindShortestSeparator(std::string* start, const Slice& limit) const = 0;  
  13.   
  14. // Changes *key to a short string >= *key.  
  15. // Simple comparator implementations may return with *key unchanged,  
  16. // i.e., an implementation of this method that does nothing is correct.  
  17. //这个函数的作用就是:找一个>= *key的短字符串  
  18. //简单的comparator实现可能不改变*key,这也是正确的  
  19. 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 wisecomparator是如何实现FindShortestSeparator()的,没什么特别的,代码+注释如下:

[cpp] view plaincopy
  1. virtual void FindShortestSeparator(std::string* start, const Slice& limit) const   
  2. {  
  3.     // 首先计算共同前缀字符串的长度  
  4.      size_t min_length = std::min(start->size(), limit.size());  
  5.     size_t diff_index = 0;  
  6.     while ((diff_index < min_length) && ((*start)[diff_index] == limit[diff_index])) {  
  7.       diff_index++;  
  8.     }  
  9.     if (diff_index >= min_length) {  
  10.       // 说明*start是limit的前缀,或者反之,此时不作修改,直接返回  
  11.      } else {  
  12.        // 尝试执行字符start[diff_index]++,设置start长度为diff_index+1,并返回  
  13.         // ++条件:字符< oxff 并且字符+1 < limit上该index的字符  
  14.         uint8_t diff_byte = static_cast<uint8_t>((*start)[diff_index]);  
  15.         if (diff_byte < static_cast<uint8_t>(0xff) &&  
  16.           diff_byte + 1 < static_cast<uint8_t>(limit[diff_index])) {  
  17.             (*start)[diff_index]++;  
  18.             start->resize(diff_index + 1);  
  19.             assert(Compare(*start, limit) < 0);  
  20.         }  
  21.     }  
  22. }  

最后是FindShortSuccessor(),这个更简单了,代码+注释如下:

[cpp] view plaincopy
  1. virtual void FindShortSuccessor(std::string* key) const   
  2. {  
  3.     // 找到第一个可以++的字符,执行++后,截断字符串;  
  4.     // 如果找不到说明*key的字符都是0xff啊,那就不作修改,直接返回  
  5.     size_t n = key->size();  
  6.     for (size_t i = 0; i < n; i++) {  
  7.       const uint8_t byte = (*key)[i];  
  8.       if (byte != static_cast<uint8_t>(0xff)) {  
  9.         (*key)[i] = byte + 1;  
  10.         key->resize(i+1);  
  11.         return;  
  12.       }  
  13.     }  
  14. }  

Leveldb内建的基于Byte wisecomparator类就这么多内容了,下面再来看看InternalKeyComparator

4.6.2 InternalKeyComparator

从上面对Internal Key的讨论可知,由于它是由user keysequence numbervalue 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是唯一的,但是直接取出8bytesequence number | value type,然后做比较更方便,不需要再次移位提取出7bytesequence number,又何乐而不为呢。这也是把value type安排在低7byte的好处吧,排序的两个依据就是user keysequence 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;

函数实现:

[cpp] view plaincopy
  1. // 尝试更新user key,基于指定的user comparator  
  2. Slice user_start = ExtractUserKey(*start);  
  3. Slice user_limit = ExtractUserKey(limit);  
  4. std::string tmp(user_start.data(), user_start.size());  
  5. user_comparator_->FindShortestSeparator(&tmp, user_limit);  
  6. if(tmp.size()<user_start.size()&&user_comparator_->Compare(user_start, tmp)<0) {  
  7.   // user key在物理上长度变短了,但其逻辑值变大了.生产新的*start时,  
  8.    // 使用最大的sequence number,以保证排在相同user key记录序列的第一个  
  9.    PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));  
  10.   assert(this->Compare(*start, tmp) < 0);  
  11.   assert(this->Compare(tmp, limit) < 0);  
  12.   start->swap(tmp);  
  13. }  

接下来是FindShortSuccessor(std::string* key)函数,该函数取出Internal Key中的user key字段,根据user指定的comparator找到并替换key,如果key被替换了,就用新的key更新Internal Key,并使用最大的sequence number。否则保持不变。实现逻辑如下:

[cpp] view plaincopy
  1. Slice user_key = ExtractUserKey(*key);  
  2. // 尝试更新user key,基于指定的user comparator  
  3. std::string tmp(user_key.data(), user_key.size());  
  4. user_comparator_->FindShortSuccessor(&tmp);  
  5. if(tmp.size()<user_key.size() && user_comparator_->Compare(user_key, tmp)<0) {  
  6.    // user key在物理上长度变短了,但其逻辑值变大了.生产新的*start时,  
  7.     // 使用最大的sequence number,以保证排在相同user key记录序列的第一个  
  8.     PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));  
  9.    assert(this->Compare(*key, tmp) < 0);  
  10.    key->swap(tmp);  
  11. }  

4.7 Memtable::Insert()

把相关的KeyKey Comparator都弄清楚后,是时候分析memtable本身了。首先是向memtable插入记录的接口,函数原型如下:

void Add(SequenceNumber seq, ValueType type, const Slice& key, const Slice& value);

代码实现如下:

[cpp] view plaincopy
  1. // KV entry字符串有下面4部分连接而成  
  2.    //  key_size     : varint32 of internal_key.size()  
  3. //  key bytes    : char[internal_key.size()]  
  4. //  value_size   : varint32 of value.size()  
  5. //  value bytes  : char[value.size()]  
  6. size_t key_size = key.size();  
  7. size_t val_size = value.size();  
  8. size_t internal_key_size = key_size + 8;  
  9. const size_t encoded_len =  
  10.     VarintLength(internal_key_size) + internal_key_size +  
  11.     VarintLength(val_size) + val_size;  
  12. char* buf = arena_.Allocate(encoded_len);  
  13. char* p = EncodeVarint32(buf, internal_key_size);  
  14. memcpy(p, key.data(), key_size);  
  15. p += key_size;  
  16. EncodeFixed64(p, (s << 8) | type);  
  17. p += 8;  
  18. p = EncodeVarint32(p, val_size);  
  19. memcpy(p, value.data(), val_size);  
  20. assert((p + val_size) - buf == encoded_len);  
  21. 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

4.8 Memtable::Get()

Memtable的查找接口,根据一个LookupKey找到响应的记录,函数声明:

bool MemTable::Get(const LookupKey& key, std::string* value, Status* s)

函数实现如下:

[cpp] view plaincopy
  1. Slice memkey = key.memtable_key();  
  2. Table::Iterator iter(&table_);  
  3. iter.Seek(memkey.data()); // seek到value>= memkey.data()的第一个记录  
  4.  if (iter.Valid()) {  
  5.    // 这里不需要再检查sequence number了,因为Seek()已经跳过了所有  
  6.    // 值更大的sequence number了  
  7.    const char* entry = iter.key();  
  8.    uint32_t key_length;  
  9.    const char* key_ptr = GetVarint32Ptr(entry, entry+5, &key_length);  
  10.    // 比较user key是否相同,key_ptr开始的len(internal key) -8 byte是user key  
  11.    if (comparator_.comparator.user_comparator()->Compare(  
  12.        Slice(key_ptr, key_length - 8),   
  13.        key.user_key()) == 0) {  
  14.       // len(internal key)的后8byte是 |sequence number | value type|  
  15.       const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);  
  16.       switch (static_cast<ValueType>(tag & 0xff)) {  
  17.       case kTypeValue: {// 只取出value  
  18.         Slice v = GetLengthPrefixedSlice(key_ptr + key_length);  
  19.         value->assign(v.data(), v.size());  
  20.         return true;  
  21.       }  
  22.       case kTypeDeletion:  
  23.         *s = Status::NotFound(Slice());  
  24.         return true;  
  25.     }  
  26.   }  
  27. }  
  28. 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 keysequence number,其排序比较依据依次是:

>1首先根据user key按升序排列

>2然后根据sequence number按降序排列

>3最后根据value type按降序排列(这个其实无关紧要)

0 0