|
|
@ -25,14 +25,14 @@ |
|
|
|
具体指:基于 levelDB扩展 value 的结构,使其可以包含多个字段,并通过这些字段实现类似数据库列查询的功能。 |
|
|
|
|
|
|
|
#### 2.1.1 实验要求: |
|
|
|
字段存储: |
|
|
|
1. 将 LevelDB 中的 value 组织成字段数组,每个数组元素对应一个字段(字段名:字段值)。 |
|
|
|
2. 字段会被序列化为字符串,然后插入LevelDB。 |
|
|
|
3. 这些字段可以通过解析字符串得到,字段名与字段值都是字符串类型。 |
|
|
|
4. 允许任意调整字段。 |
|
|
|
1. 字段存储: |
|
|
|
+ 将 LevelDB 中的 value 组织成字段数组,每个数组元素对应一个字段(字段名:字段值)。 |
|
|
|
+ 字段会被序列化为字符串,然后插入LevelDB。 |
|
|
|
+ 这些字段可以通过解析字符串得到,字段名与字段值都是字符串类型。 |
|
|
|
+ 允许任意调整字段。 |
|
|
|
|
|
|
|
查询功能: |
|
|
|
实现通过字段值查询对应的 key。 |
|
|
|
2. 查询功能: |
|
|
|
+ 实现通过字段值查询对应的 key。 |
|
|
|
|
|
|
|
#### 2.1.2 实验内容 |
|
|
|
1. 数据存储与解析: 每个 value 存储为一个字符串数组,数组中的每个元素代表一个字段。 |
|
|
@ -41,7 +41,7 @@ |
|
|
|
**设计思路:** |
|
|
|
1. 使用 Field 存储属性和值,使用 FieldArray 存储多个 Field; |
|
|
|
2. 函数 SerializeValue 把字段数组序列化为字符串; |
|
|
|
3. 函数 ParseValue 把字符串反序列化为字段数组; |
|
|
|
3. 函数 DeserializeValue 把字符串反序列化为字段数组; |
|
|
|
4. 函数 FindKeysByField 根据传入的字段名和字段的值找到对应的key。 |
|
|
|
### 2.1.3 实验进度以及实验结果 |
|
|
|
#### 实验进度 |
|
|
@ -62,9 +62,9 @@ |
|
|
|
对于vlog文件,我们在内存中维护一个bitmap,用来表示每一个slot的使用情况,并在插入和GC删除kv时进行动态的分配和释放。对于vlog文件的GC,我们用一个后台线程来扫描所有vlog的discard计数器。当某些vlog的discard计数器超过某个阈值(比如1024),我们就对这些vlog文件进行GC过程,当GC完成之后将slot_page中的slot元数据进行更新,再将原来的vlog文件进行删除,GC过程就完成了。 |
|
|
|
|
|
|
|
##### 2.2.1 相关代码文件 |
|
|
|
- [`/db/db_impl.cc`](./db/db_impl.cc): 修改 DBImpl::Get, DBImpl::Put 和 DBImpl::Delete |
|
|
|
- [`/db/db_impl.h`](./db/db_impl.h): 添加两个结构体 SlotPage *slot_page_; VlogSet *vlog_set_; |
|
|
|
- |
|
|
|
- [`/db/db_impl.cc`](./db/db_impl.cc): 修改函数 DBImpl::Get, DBImpl::Put 和 DBImpl::Delete,添加函数 Put_fields, Get_fields, get_slot_num,SerializeValue, DeserializeValue |
|
|
|
- [`/db/db_impl.h`](./db/db_impl.h): 添加两个结构体 SlotPage *slot_page_; VlogSet *vlog_set_ ,添加增加的相关函数的声明 |
|
|
|
- |
|
|
|
- [`/db/shared_lock.h`](./db/shared_lock.h) 定义了一个 SharedLock 类,用于实现读写锁机制,包含四种操作:soft_lock():获取共享读锁,确保在没有写操作时允许多个读操作并发进行;soft_unlock():释放共享读锁;hard_lock():获取独占写锁,确保只有当没有其他读写操作时,允许写入操作进行;hard_unlock():释放独占写锁。 |
|
|
|
- [`/db/slotpage.h`](./db/slotpage.h) |
|
|
|
- [`/db/threadpool.h`](./db/threadpool.h) |
|
|
@ -73,37 +73,47 @@ |
|
|
|
- [`/db/vlog_gc.h`](./db/vlog_gc.h) |
|
|
|
- [`/db/vlog_set.cpp`](./db/vlog_set.cpp) |
|
|
|
- [`/db/vlog_set.h`](./db/vlog_set.h) |
|
|
|
- |
|
|
|
- |
|
|
|
- [`/test/db_test3.cc`](./test/db_test3.cc):测试 value 的字段功能 |
|
|
|
- [`/test/db_test4.cc`](./test/db_test4.cc) |
|
|
|
- [`/test/db_test5.cc`](./test/db_test5.cc) |
|
|
|
- |
|
|
|
- |
|
|
|
- [`CMakeLists.txt`](CMakeLists.txt):添加可执行文件 |
|
|
|
##### 2.2.1 具体流程 |
|
|
|
写入流程 |
|
|
|
```` |
|
|
|
Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { |
|
|
|
Status DBImpl::Put_Fields(const WriteOptions& opt, const Slice& key, |
|
|
|
const FieldArray& fields) { |
|
|
|
// TODO(begin): allocate slot_num in slotpage and put value in vlog |
|
|
|
|
|
|
|
// 将字段数组序列化 |
|
|
|
size_t slot_num = slot_page_->alloc_slot(); |
|
|
|
std::string slot_num_str((char *)&slot_num, sizeof(size_t)); |
|
|
|
// size_t slot_num_str_num; |
|
|
|
// std::memcpy(&slot_num_str_num, slot_num_str.c_str(), sizeof(size_t)); |
|
|
|
// std::cout << "slot_num_str_num: " << slot_num_str_num << std::endl; |
|
|
|
std::string serialized_value = SerializeValue(fields, slot_num_str); |
|
|
|
// std::cout << "Put_Fields: " << key.ToString() << " " << serialized_value << std::endl; |
|
|
|
struct slot_content sc; |
|
|
|
vlog_set_->put_value(&sc.vlog_num, &sc.value_offset, val); |
|
|
|
vlog_set_->put_value(&sc.vlog_num, &sc.value_offset, serialized_value); |
|
|
|
slot_page_->set_slot(slot_num, &sc); |
|
|
|
|
|
|
|
char data[sizeof(size_t)]; |
|
|
|
memcpy(data, &slot_num, sizeof(size_t)); |
|
|
|
Slice slot_val(data, sizeof(data)); |
|
|
|
|
|
|
|
return DB::Put(o, key, slot_val); |
|
|
|
// return DB::Put(opt, key, slot_val); |
|
|
|
return DB::Put(opt, key, serialized_value); |
|
|
|
// TODO(end) |
|
|
|
} |
|
|
|
} |
|
|
|
```` |
|
|
|
1. 调用编码函数,将 val 编码为字符串 |
|
|
|
2. 在 slot_page_ 中为 K-V 对分配一个 slot ,编号为 slot_num |
|
|
|
3. 实例化 slot_content 结构体 sc |
|
|
|
4. 以 sc 中的 vlog_num(vlog编号) 和 value_offset(在vlog中的偏移量) 为参数,将字符串写入 vlog 中 |
|
|
|
5. 将 slot_content 中的内容赋值给 slot_num |
|
|
|
6. 将 slot_num 作为 key 的 value 写入数据库中 |
|
|
|
1. 为当前 KV 对分配一个 size_t 类型的 slot_num; |
|
|
|
2. 将 slot_num 转化为字符串形式 slot_num_str; |
|
|
|
3. 调用 SerializeValue 函数将字段数组和 slot_num_str 序列化为字符串 serialized_value; |
|
|
|
4. 实例化 slot_content 结构体 sc; |
|
|
|
5. 调用 put_value 函数,以 sc 中的 vlog_num(vlog编号) 和 value_offset(在vlog中的偏移量) 为参数,将字符串 serialized_value 写入 vlog 中; |
|
|
|
6. 调用 set_slot 函数,将 slot_content 中的内容赋值给 slot_num; |
|
|
|
7. 将 slot_num 作为 value 写入数据库中; |
|
|
|
|
|
|
|
读取流程 |
|
|
|
```` |
|
|
@ -146,7 +156,7 @@ Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { |
|
|
|
|
|
|
|
`slot_page: | slot0:{vlog_no(定长), offset(定长)}, slot1:{vlog_no, offset}, ... | ` |
|
|
|
|
|
|
|
`value 的格式:| attr个数(定长) | attr1_name的长度(定长) | attr1_name(变长) | attr1_value的长度(定长) | attr1_value(变长) | ... |` |
|
|
|
`value 的格式:|value 长度 | slot_num | attr个数(定长) | attr1_name的长度(定长) | attr1_name(变长) | attr1_value的长度(定长) | attr1_value(变长) | ... |` |
|
|
|
|
|
|
|
对于每一次读取,用户线程先读取lsm tree中key的slot_num下标,然后到slot_page中读取对应的slot内容(**每一个slot都是定长的**),之后再在这个slot中读取value所在的vlog文件号和偏移量offset,之后到对应的vlog文件中读取value。 |
|
|
|
|
|
|
@ -155,15 +165,15 @@ Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { |
|
|
|
|
|
|
|
### 4. 接口设计 |
|
|
|
#### 4.1 在 LevelDB 的 value 中实现字段功能 |
|
|
|
1. std::string SerializeValue(const FieldArray& fields) |
|
|
|
1. std::string SerializeValue(const FieldArray& fields, std::string slot_num_str) |
|
|
|
|
|
|
|
**功能:** 将字段数组序列化为字符串 |
|
|
|
**功能:** 将字段数组和 slot_num_str 序列化为字符串 |
|
|
|
|
|
|
|
**输入:** 字段名和字段的值组成的字段数组 |
|
|
|
**输入:** 字段名和字段的值组成的字段数组 和 slot_num_str,即为该 KV 对分配的 slot_num 的字符串形式 |
|
|
|
|
|
|
|
**输出:** 序列化后的字符串 |
|
|
|
|
|
|
|
2. FieldArray ParseValue(const std::string& value_str) |
|
|
|
2. FieldArray DeserializeValue(const std::string& value_str) |
|
|
|
|
|
|
|
**功能:** 将字符串反序列化为字段数组 |
|
|
|
|
|
|
@ -179,15 +189,16 @@ Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { |
|
|
|
|
|
|
|
**输出:** 包含该字段和字段数组的 key,由于可能不只有一个,所以返回值为 vector |
|
|
|
|
|
|
|
4. Put_Fields (待实现) |
|
|
|
4. Put_Fields(const WriteOptions& opt, const Slice& key, |
|
|
|
const FieldArray& fields) |
|
|
|
|
|
|
|
**功能:** 仿照Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value),通过调用序列化函数,实现以字段形式插入 value |
|
|
|
**功能:** 仿照Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value),调用序列化函数,实现以字段形式插入 value |
|
|
|
|
|
|
|
**输入:** 数据库名,字段名和字段的值 |
|
|
|
5. Get_Fields(const ReadOptions& options, const Slice& key, |
|
|
|
FieldArray* fields) |
|
|
|
|
|
|
|
**输出:** 包含该字段和字段数组的 key,由于可能不只有一个,所以返回值为 vector |
|
|
|
**功能:** 仿照Status DB::Get(const WriteOptions& opt, const Slice& key, const Slice& value),读取key对应的 value 之后,通过调用反序列化函数,将 value 反序列化为字段数组,并存到 fields 中 |
|
|
|
|
|
|
|
5. Get_Fields (待实现) |
|
|
|
#### 4.2 实现KV分离 |
|
|
|
1. 搜索slot_page文件: Status find_slot(const Slice& key, Slot *slot); |
|
|
|
2. 搜索vlog文件: Status find_value(Slot *slot); |
|
|
|