【注】本文参考了sparkliang的专栏的Leveldb源码分析--3并进行了一定的重组和排版
经过上一篇文章的分析我们队leveldb的插入流程有了一定的认识,而该文设计最多的又是Batch的概念。这篇文章本来应该顺理成章的介绍Batch相关流程和结构了,但是Batch涉及到了一些编码和Key相关的概念,所以我觉得应该先理清这方面的概念有助于大家更容易理解后面的内容。
在dbformat.h/cc文件中我们首先看到的是
typedef uint64_t SequenceNumber;
struct ParsedInternalKey { Slice user_key; SequenceNumber sequence; ValueType type; ... };
而InternalKey只包含了一个string类型的对象,但是我们可以从其构造函数可以得出其主要由ParsedInternalKey的三个字段编码而来
user_key (string) | sequence (7 byte) | value_type (1 byte)
由此可sequence number大小是7 byte,sequence number是所有基于op log系统的关键数据,它唯一指定了不同操作的时间顺序。
把user key放到前面的原因是,这样对同一个user key的操作就可以按照sequence number顺序连续存放了,这样在后继的Compaction过程中就可以容易的对相同Key的操作进行合并了。另外用户可以为user key定制比较函数,系统默认是字节序的。
另外在Memtable的查询时又涉及到了LookupKey,它也是由User Key和Sequence Number组合而成的,从其构造函数:LookupKey(const Slice& user_key, SequenceNumber s)中分析出LookupKey的格式为:
Size (int32变长)| User key (string) | sequence number (7 byte) | value type (1 byte)
而LookupKey又有几个函数
// Return a key suitable for lookup in a MemTable. 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); }
memtable_key是在查找过程中传递给SkipList的Comparator进行查找比较的时候用的,前面提到过目前默认的Comparator实现是会将其中的User key抽取出来按照字节序进行比较。
在这些key的组成和编码过程中我们又接触到了一些leveldb 的内部编码方式,比如Varint32(int32变长)、Fixed32等,具体编码方式在coding.cc中,这些编码方式都是根据Google的protobuffer的来的。这个协议在Google内部的数据传输中得到了广泛的使用,另外在一些电信和移动通信领域也得到了一些借鉴和发展。如果对其具体的实现细节感兴趣,可以参见https://developers.google.com/protocol-buffers/docs/overview