leveldb中数据存储过程
当向leveldb
写入数据时,首先将数据写入log
文件,然后在写入memtable
内存中。log
文件主要是用在当断电时,内存中数据会丢失,数据可以从log
文件中恢复。当memtable
数据达到一定大小,会变为immemtable
,并自动生成新的memtable
,然后log
文件也生成一个新的log
文件。Immutable Memtable
则被新的线程Dump到磁盘中,Dump结束则该Immutable Memtable就可以释放了。memtable
提供了写入KV记录,删除以及读取KV记录的接口,但是事实上memtable
并不执行真正的删除操作,删除某个Key的Value在memtable
内是作为插入一条记录实施的,但是会打上一个Key
的删除标记,真正的删除操作在后面的 Compaction
过程中(是不是感觉和前面介绍的skiplist很相似,没错,memtable
的核心结构就是skiplist
)
leveldb 中的 Key
user_key
Slice
类,实质上就是一个string
,只不过稍微封装了一下
class Slice {
public:
//something...和本文关系不大,不全部贴出来了
private:
const char* data_;
size_t size_;
};
ParsedInternalKey
struct ParsedInternalKey {
Slice user_key;
SequenceNumber sequence;
ValueType type;
ParsedInternalKey() { } // Intentionally left uninitialized (for speed)
ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t)
: user_key(u), sequence(seq), type(t) { }
std::string DebugString() const;
};
ParsedInternalKey
就是一个struct
,其中SequenceNumber
和ValueType
定义如下:
enum ValueType {
kTypeDeletion = 0x0,
kTypeValue = 0x1
};
typedef uint64_t SequenceNumber;
InternalKey
class InternalKey {
private:
std::string rep_;
public:
InternalKey() { } // Leave rep_ as empty to indicate it is invalid
InternalKey(const Slice& user_key, SequenceNumber s, ValueType t) {
AppendInternalKey(&rep_, ParsedInternalKey(user_key, s, t));
}
void DecodeFrom(const Slice& s) { rep_.assign(s.data(), s.size()); }
Slice Encode() const {
assert(!rep_.empty());
return rep_;
}
Slice user_key() const { return ExtractUserKey(rep_); }
void SetFrom(const ParsedInternalKey& p) {
rep_.clear();
AppendInternalKey(&rep_, p);
}
void Clear() { rep_.clear(); }
std::string DebugString() const;
};
通过源码我们可以看到InternalKey
本质上也是一个字符串,这个字符串是由ParsedInternalKey
转换而来的。下面看看具体的转换函数
void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
result->append(key.user_key.data(), key.user_key.size());
PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
}
static uint64_t PackSequenceAndType(uint64_t seq, ValueType t) {
assert(seq <= kMaxSequenceNumber);
assert(t <= kValueTypeForSeek);
return (seq << 8) | t;
}
实质上就是一个字符串拼接的过程。(其中涉及到coding)
根据这个我们也能得到InternalKey
的组成形式实际上就是
| User key (string) | sequence number (7 bytes) | value type (1 byte) |
同样从InternalKey
中我们能获得user_key
,操作也十分简单
inline Slice ExtractUserKey(const Slice& internal_key) {
assert(internal_key.size() >= 8);
return Slice(internal_key.data(), internal_key.size() - 8);
}
从InternalKey
中我们能获得TypeValue
:
inline ValueType ExtractValueType(const Slice& internal_key) {
assert(internal_key.size() >= 8);
const size_t n = internal_key.size();
uint64_t num = DecodeFixed64(internal_key.data() + n - 8);
unsigned char c = num & 0xff;
return static_cast<ValueType>(c);
}
LookupKey
memtable
的查询接口传入的就是LookuoKey
bool Get(const LookupKey& key, std::string* value, Status* s);
先看一下LookupKey
是怎么定义的
class LookupKey {
public:
LookupKey(const Slice& user_key, SequenceNumber sequence);
~LookupKey();
Slice memtable_key() const { return Slice(start_, end_ - start_); }
Slice internal_key() const { return Slice(kstart_, end_ - kstart_); }
Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); }
private:
const char* start_;
const char* kstart_;
const char* end_;
char space_[200]; // Avoid allocation for short keys
// No copying allowed
LookupKey(const LookupKey&);
void operator=(const LookupKey&);
};
再看看构造函数
LookupKey::LookupKey(const Slice& user_key, SequenceNumber s) {
size_t usize = user_key.size();
size_t needed = usize + 13; // A conservative estimate
char* dst;
if (needed <= sizeof(space_)) {
dst = space_;
} else {
dst = new char[needed];
}
start_ = dst;
dst = EncodeVarint32(dst, usize + 8);
kstart_ = dst;
memcpy(dst, user_key.data(), usize);
dst += usize;
EncodeFixed64(dst, PackSequenceAndType(s, kValueTypeForSeek));
dst += 8;
end_ = dst;
}
这样的话不难分析出,LookupKey
的结构如下:
| Size (varint32)| User key (string) | sequence number (7 bytes) | value type (1 byte) |
这里的size
其实就是user_key
的长度加8
.
start_
是LookupKey
字符串的开始,end_
是结束,kstart_
是user key
字符串的起始地址
======
Key分析完了=。=明天再继续分析memtable,啦啦啦啦啦啦啦