|
|
@ -837,4 +837,91 @@ fieldDb功能实现(没有并发和恢复)|12.10|李度、陈胤遒 |
|
|
|
lab2测试、并发完成|12.20|李度 |
|
|
|
恢复|12.25|李度、陈胤遒 |
|
|
|
整体系统整合+测试|12.28|李度、陈胤遒、高宇菲 |
|
|
|
性能测试|1.1|李度、陈胤遒、高宇菲 |
|
|
|
性能测试|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<uint64_t> deleted_kvlogs_; |
|
|
|
std::vector<FileMetaData> new_kvlogs_; |
|
|
|
/*****************************/ |
|
|
|
} |
|
|
|
|
|
|
|
class VersionSet{ |
|
|
|
/*****************************/ |
|
|
|
void AddLiveKVLogs(std::set<uint64_t>* live_kvlogs); |
|
|
|
std::vector<FileMetaData*> 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分离方式的最简要的介绍 |