在Leveldb中,所有内存中的KV数据都存储在Memtable中,物理disk则存储在SSTable中。在系统运行过程中,如果Memtable中的数据占用内存到达指定值(Options.write_buffer_size),则Leveldb就自动将Memtable转换为Memtable,并自动生成新的Memtable。
internalKey的内部组成格式为:
| User key (string) | sequence number (7 bytes) | value type (1 byte) |
ParseInternalKey函数解析InternalKey,AppendInternalKey生成一个internalkey。所以在ParsedInternalKey结构体中的前三个成员就是拆分的InternalKey,一目了然:
struct ParsedInternalKey {
Slice user_key;
SequenceNumber sequence;
ValueType type;
...
}
其实就是一个字符串
LookupKey生成一个key,它内部的构造:
| Size (int32变长)| User key (string) | sequence number (7 bytes) | value type (1 byte) |
Memtable Key用字符串(slice类型)的形式返回这个值。
有三个函数导出各个key:
Slice memtable_key() const { return Slice(start_, end_ - start_); }
// Return an internal key (suitable for passing to an internal iterator)
Slice internal_key() const { return Slice(kstart_, end_ - kstart_); }
// Return the user key
Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); }
在comparator中有两个方法需要注意:
FindShortestSeparator(std::string* start, const Slice& limit);
FindShortSuccessor(std::string* key)
从实现上来看,leveldb中一共有两个具体实现:
class BytewiseComparatorImpl : public Comparator { ... }
class InternalKeyComparator : public Comparator { ... }
其中BytewiseComparatorImpl关于上述两个函数的实现很简单,没有什么值得讨论的地方。由于InternalKey的内部结构,InternalKeyComparator类中,特意添加了一个user_comparator_的成员用来专门对user key做比较。
class InternalKeyComparator : public Comparator {
private:
const Comparator* user_comparator_;
...
}
比较的顺序为:
首先根据user key按升序排列
然后根据sequence number按降序排列
最后根据value type按降序排列
主要就是按照| VarInt(Internal Key size) len | internal key |VarInt(value) len |value|的格式,pack到一块内存中,然后调用:
table_.Insert(buf)
来插入到memtable中去。
核心逻辑是一个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())。
首先在skiplist的构造函数里,会通过NewNode(0, kMaxHeight)初始化head_,然后将node中的next_数组元素初始化为0。注意next_数组元素是class AtomicPointer类型,其实就是为了安全的操作rep_,防止data race造成bug。max_height_被初始化为1。
class skiplist {
// skiplist class private data
private:
Node* const head_;
port::AtomicPointer max_height_;
}
struct Node {
private:
// node struct private data
port::AtomicPointer next_[1];
}
上面的第一个class,就是所谓的skiplist,在它的private成员里,head_对应下图HEAD,而下图中的TAIL其实在leveldb的中是用的NULL。
max_heght_在本示例图中是3,即level0-level3。
第二类,也就是struct Node。其实代表一个实节点,那么怎样的节点叫实际节点?就是每个字符对应的节点。我们从level0上看,我们实际有
a-i等9个节点,那么意味着有9个node。那level1和level2算什么?这就是skiplist的特色了。在下面马上就会讨论到。
HEAD TAIL
|----------------------------->e-----------------------------| (level 2)
| | |
|----------------->c---------->e---------->g-----------------| (level 1)
| | | | |
|<---->a<--->b<--->c<--->d<--->e<--->f<--->g<--->h<--->i<--->| (level 0)
随机生成一个1到kMaxHeight之间的高度值。这个值就是level1和level2的生成谜底。每个新生成的node在进行insert时,需要预先进行一个RandomHeight的动作,算出当前的node有占几层,也就是level为多少。除了c,e,g其他的node之所以只有一层,就是因为RandomHeight算出的level为1。
在insert时,leveldb使用的是升序。所以需要首先调用FindGreaterOrEqual来找到合适的位置,”Greater Or Equal“的含义很明显体现了升序的意思,跳过less的,找第一个大于等于自己的node为目标。在具体查找过程中,首先从最顶层开始查找,在没找到合适的位置时,会逐级向下递减层级,当在某一层中找打了合适的插入位置,prev就记录下当前level在当前level中插入节点,所需要的前驱位置。由于leveldb中的skiplist本质上每层level是一个单链表,所以要做插入动作,必须要知道前驱节点,而后继节点在前驱节点的next里。
原理同FindGreaterOrEqual。
通过FindGreaterOrEqual找到了合适的位置,然后就需要计算自己的level,这里假设level 小于当前的max_height_。当然如果大于的话,需要对head_做一定的处理,很简单不做讨论。插入的操作很简单:
x = NewNode(key, height);
for (int i = 0; i < height; i++) {
x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i));
prev[i]->SetNext(i, x);
}
这几行代码里,首先需要解释prev是做什么的。它实际是在调用FindGreaterOrEqual时生成的。我们回到看上面的图,演绎一下e节点的插入过程。在FindGreaterOrEqual执行过程,prev[i]指向了c节点位于level1的一个位置,同时prev[i]->NoBarrier_Next指向的是g在level1中的位置。那么e在level1中插入一个占位,然后执行经典的单链表插入指针修改动作。for循环的两句其实就是下面的原理,只不过数据结构复杂一点罢了:
x -> next = p->next;
p->next = x;