Memtable
leveldb数据写入时并非直接落盘,而是先保存在内存中,在内存中的数据按key进行排序。当内存中的数据达到一定大小时,再将这批数据批量写入磁盘。在内存中的数据结构我们称之为Memtable,本节将介绍Memtable的实现。 先看代码:
class MemTable {
public:
explicit MemTable(const InternalKeyComparator& comparator);
MemTable(const MemTable&) = delete;
MemTable& operator=(const MemTable&) = delete;
void Ref() { ++refs_; }
void Unref() {
--refs_;
assert(refs_ >= 0);
if (refs_ <= 0) {
delete this;
}
}
size_t ApproximateMemoryUsage();
Iterator* NewIterator();
void Add(SequenceNumber seq, ValueType type, const Slice& key,
const Slice& value);
bool Get(const LookupKey& key, std::string* value, Status* s);
private:
friend class MemTableIterator;
friend class MemTableBackwardIterator;
struct KeyComparator {
const InternalKeyComparator comparator;
explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) {}
int operator()(const char* a, const char* b) const;
};
typedef SkipList<const char*, KeyComparator> Table;
~MemTable();
KeyComparator comparator_;
int refs_;
Arena arena_;
Table table_;
};
上述代码中Add和Get函数即为向Memtable中写入和读取数据接口。 void Add(SequenceNumber seq, ValueType type, const Slice& key, const Slice& value);第一个参数seq即全局唯一的序号,每进行一次更新操作序号加1,第二个参数type是表示该操作的类型:包括PUT和DELETE两种,key表示要添加或删除的key,value表示要添加的key对应的value(当type为DELETE时value为空)。 bool Get(const LookupKey& key, std::string* value, Status* s);当Memtable中存在所查找的key时,value存储其值,函数返回true;当Memtable中存在所查找key的删除标记时,s中存储NotFound的状态值,函数返回true;其他情况,函数返回false。 Memtable具体如何实现呢?主要在它的成员变量Table table_,即SkipList。
class SkipList {
private:
struct Node;
public:
explicit SkipList(Comparator cmp, Arena* arena);
SkipList(const SkipList&) = delete;
SkipList& operator=(const SkipList&) = delete;
void Insert(const Key& key);
bool Contains(const Key& key) const;
class Iterator {
public:
explicit Iterator(const SkipList* list);
bool Valid() const;
const Key& key() const;
void Next();
void Prev();
void Seek(const Key& target);
void SeekToFirst();
void SeekToLast();
private:
const SkipList* list_;
Node* node_;
};
private:
enum { kMaxHeight = 12 };
inline int GetMaxHeight() const {
return max_height_.load(std::memory_order_relaxed);
}
Node* NewNode(const Key& key, int height);
int RandomHeight();
bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
bool KeyIsAfterNode(const Key& key, Node* n) const;
Node* FindGreaterOrEqual(const Key& key, Node** prev) const;
Node* FindLessThan(const Key& key) const;
Node* FindLast() const;
Comparator const compare_;
Arena* const arena_;
Node* const head_;
std::atomic<int> max_height_;
Random rnd_;
};
Skiplist类提供了两个主要方法,Insert和Contains。前者即向Skiplist中插入一个key,后者则是判断Skiplist中是否存在这个key。当然还提供了一个Iterator类,用于迭代Skiplist中的所有key值。
Skiplist
接下来介绍一下Skiplist: Skiplist故名思义,即跳跃的链表。传统的链表我们要查找时只能依次遍历,效率很低;而skiplist在查找时,则可以跳跃的方式遍历,从而提高了查询效率。其原理是怎么样的呢? 跳表是由William Pugh发明。他在 Communications of the ACM June 1990, 33(6) 668-676 发表了Skip lists: a probabilistic alternative to balanced trees,在该论文中详细解释了跳表的数据结构和插入删除操作。 跳表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。
下面来研究一下跳表的核心思想:
先从链表开始,如果是一个简单的链表,那么我们知道在链表中查找一个元素I的话,需要将整个链表遍历一次。
如果是说链表是排序的,并且节点中还存储了指向前面第二个节点的指针的话,那么在查找一个节点时,仅仅需要遍历N/2个节点即可。
这基本上就是跳表的核心思想,其实也是一种通过“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。而leveldb的Memtable即使用了基于该思想的skiplist。
|