diff --git a/README.md b/README.md index 20dba8a..d1eaf1a 100644 --- a/README.md +++ b/README.md @@ -837,4 +837,91 @@ fieldDb功能实现(没有并发和恢复)|12.10|李度、陈胤遒 lab2测试、并发完成|12.20|李度 恢复|12.25|李度、陈胤遒 整体系统整合+测试|12.28|李度、陈胤遒、高宇菲 -性能测试|1.1|李度、陈胤遒、高宇菲 \ No newline at end of file +性能测试|1.1|李度、陈胤遒、高宇菲 + +## 7. KV分离 +由于在讨论的时候我们想到了一种感觉非常思路清晰、实现简单的实现kv分离的方案,在基本完成了实验报告后,我们抽空花了一天半基本上实现了kv分离,代码总计700行左右下面对于设计进行最简单的介绍 + +### 7.1 KVLog +我们实现kv分离的主要途径是使用KVLog和FilePointer。KVLog和SSTable在生成的时候是一一对应的,但是并不随着SSTable的合并而合并。KVLog是一个如下结构的文件类型: +``` +Batchsize|WriteBatch|Batchsize|WriteBatch|...... +``` +`FilePointer`包含三个信息,分别是KVLog的文件编号,偏移量以及信息的长度。 + +从逻辑结构来看,非常类似于leveldb的log,两者本质上存储的都是一系列的WriteBatch。但是不同的是,leveldb的log会每4KB对于内容进行切分,这会导致无法直接通过`(偏移量,长度)`读出内容,而KVLog是连续的存储,是可以直接通过`(偏移量,长度)`读出内容的。 + +由于leveldb的写入原子性是通过基于log文件的结构完成的,且log在写入和恢复的路径中被大量的使用,因此我们不直接使用KVLog替换log,而是先将WriteBatch写入KVLog,然后将该WriteBatch对应的`FilePointer`写入log中。这样子我们只要对于涉及log的代码进行最简单的更改即可。 + +之后,我们对于写入到memtable和SSTable中的内容进行处理,将所有的value替换成相应的`FilePointer`。 + +以上就是对于写入流程的处理,部分核心代码如下: +```c++ +Status DBImpl::Write(const WriteOptions& options, WriteBatch* updates) { + /******************************/ + FilePointer fp; + //1. 将WriteBatch写入到kvlog中 + status = kvlog_->AddRecord(WriteBatchInternal::Contents(write_batch), fp); + //2. 将writebatch的filepointer写入到log中 + // status = log_->AddRecord(WriteBatchInternal::Contents(write_batch)); + char rep[8 * 3]; + EncodeFP(fp, rep); + status = log_->AddRecord(Slice(rep, 3 * 8)); + bool sync_error = false; + if (status.ok() && options.sync) { + status = logfile_->Sync(); + if (!status.ok()) { + sync_error = true; + } + } + //3. 根据write_batch里面的内容,构建kp_batch + WriteBatchInternal::ConstructKPBatch(tmp_kp_batch_, write_batch, fp); + WriteBatchInternal::SetSequence(tmp_kp_batch_,temp_seq + 1); + if (status.ok()) { + status = WriteBatchInternal::InsertInto(tmp_kp_batch_, mem_); + } + /******************************/ +} + +class KVLog; //用于KVLog的写入 +class KVLogReader;//用于读取KVLog中的每一个键值对以及相应的sequence +struct FilePointer { + uint64_t FileNumber; + uint64_t FileOffset; + uint64_t Size; +}; +``` + +### 7.2 KVLog的管理 +KVLog的管理主要参考的是SSTable的方式,采用多版本并发控制的思路。原因主要有两点:1. 有利于KVLog回收的实现;2.可以大量的复用或者参考SSTableFile的实现方式。核心代码如下: +```c++ +class VersionEdit { + /*****************************/ + void AddKVLogs(uint64_t file) { + FileMetaData f; + f.number = file; + new_kvlogs_.push_back(f); + } + + void RemoveKVLogs(uint64_t file) { + deleted_kvlogs_.insert(file); + } + std::set deleted_kvlogs_; + std::vector new_kvlogs_; + /*****************************/ +} + +class VersionSet{ + /*****************************/ + void AddLiveKVLogs(std::set* live_kvlogs); + std::vector kvlogs_; + void Apply(const VersionEdit* edit); + void SaveTo(Version* v); + /*****************************/ +} +``` + +### 7.3 KVLog的回收 +我们观察到Level0的SSTable是允许重叠的。因此,我们将KVLog的回收放在后台,同时禁止小合并将新生成SSTable放于L0以下的位置,这样可以保证L0以下的level不变。然后只回收KVLog不与mem、imm和L0SSTableu对应的kvlog。回收的时候新构建一个放在L0的SSTable以及对应的KVLog。新生成的SSTable包含的key和原来的sequence完全相同,value指向新的KVLog。这样子,随着之后的合并,自然就能够完成回收的操作也不会和并发的写入发生冲突。代码主体是`bool DBImpl::CollectKVLogs()`. + +以上就是对于我们实现KV分离方式的最简要的介绍