|
|
9 months ago | ||
|---|---|---|---|
| .github/workflows | 删除 | 2 years ago | |
| benchmarks | 删除 | 9 months ago | |
| cmake | 删除 | 6 years ago | |
| db | 删除 | 9 months ago | |
| doc | 删除 | 3 years ago | |
| helpers/memenv | 删除 | 9 months ago | |
| image | 删除 | 11 months ago | |
| include/leveldb | 删除 | 9 months ago | |
| issues | 删除 | 3 years ago | |
| port | 删除 | 2 years ago | |
| table | 删除 | 9 months ago | |
| test | 删除 | 9 months ago | |
| third_party | 删除 | 2 years ago | |
| util | 删除 | 9 months ago | |
| .clang-format | 6 years ago | ||
| .gitignore | 1 year ago | ||
| .gitmodules | 4 years ago | ||
| AUTHORS | 12 years ago | ||
| CMakeLists.txt | 9 months ago | ||
| CONTRIBUTING.md | 3 years ago | ||
| LICENSE | 14 years ago | ||
| NEWS | 14 years ago | ||
| README.md | 10 months ago | ||
| TODO | 13 years ago | ||
**项目背景:**LevelDB 项目的 KV 键值对存储信息单一;LSM-Tree 读写放大开销大,导致 LevelDB 顺序范围查询时的数据吞吐量随 Value 大小增加而急剧下降。
Field: field_name: field_value ; Fields类来管理LevelDB中的字段。其中Field是使用标准库中的std::pair<std::string, std::string> 定义了单个字段的格式,而FieldArray则是使用了std::vector<Field> 来定义一组字段。fields_这一私有成员变量,它是一个 FieldArray 类型的向量,用来存储一组字段。Fields对象。SortFields方法来确保在创建Fields对象时,各个字段会根据field_name从小到大进行排序,进而减少后续更新删除操作中会出现的通过 field_name 遍历 Fields 的耗时。UpdateField 和 UpdateFields 方法允许用户更新或插入单个或多个字段,以及DeleteField 和 DeleteFields 方法允许用户删除单个或多个字段。实现思路是通过遍历fields_来查找匹配的field_name并对其进行更新(若不存在则插入)以及删除操作(⭐在上述操作的实现中,由于字段列表的有序性,遍历时可通过比较field_name大小来提前判断该字段是否存在,对于小字段的查找尤其明显,进而有效减少搜索时间,提高搜索效率。)SerializeValue 和 ParseValue 方法分别用于将字段序列化为字符串或将字符串反序列化为字段对象。实现时是调用了conding.h文件中的PutLengthPrefixedSlice函数以及GetLengthPrefixedSlice函数,它们的作用分别是对一个string进行编码并在其前面加入长度信息和将编码后的string中的长度信息去除,提取出原始string。这两个函数不仅可以完美实现我们对字段编码的最初设计,同时也和lsm-tree里原来所有的kv数据对的编码保持一致。GetField和HasField方法用于访问特定字段和检查字段是否存在,实现思路与更新删除操作类似,也是对fields_进行遍历。[] 运算符,以提供类似字典的字段访问方式。对于常量对象,operator[] 返回字段值的副本,如果给定的字段名不存在,则返回一个空字符串,并输出错误信息。而对于非常量对象,operator[] 返回字段值的引用,并允许修改该字段值。如果给定的字段名不存在,则会插入一个新的字段,并返回新字段值的引用。FindKeysByFields 用于根据若干个字段在数据库中查找对应的键。实现上是使用LevelDB提供的API创建一个NewIterator,从数据库的第一个条目遍历到最后一个条目。事先定义了find_keys 来存储找到的键,在遍历过程中,为了避免重复处理同一个键,会先检查当前键是否已经存在于 find_keys 中。如果存在,则跳过此条目。若不存在,则提取其value部分,利用 ParseValue 方法将字符串形式的值解析为 Fields 对象,进而获得该条目对应的字段数组。再对解析后的字段数组与search_fields_进行匹配,这里支持完全匹配和部分匹配,将匹配的key存入find_keys中,最后返回find_keys。(💡Tips:在使用Iterator进行遍历时,it.key()和it.value()获取的其实是kv字符串本身,不需要我们再解码kv length和考虑tag(ktypevalue、ktypedeletion)。我们在设计之初并未考虑到这一点,是在后续测试的debug中发现了这一情况)<key, <fileno, offset>> 的形式;小 value 则与原 LevelDB SSTable 中 KV 键值对存储格式相同;offset 和 vLog 的编号 fileno ,将 <key, <fileno, offset>> 写入 WAL 和 Memtable 中;<fileno, offset> 到 LSM-Tree 中;Field & FieldArray:
using Field = std::pair<std::string, std::string>;
using FieldArray = std::vector<std::pair<std::string, std::string>>;class Fields,用于操作字段数组。
class Fields
class Fields {
private:
FieldArray fields_;
public:
/* 从 FieldArray 构造 */
explicit Fields(const FieldArray& fields);
/* 从单个 Field 构造 */
explicit Fields(const Field& field);
/* 只传参 field_name 数组的构造 */
explicit Fields(const std::vector<std::string>& field_names);
Fields() = default;
~Fields() = default;
/* 根据 field_name 从小到大进行排序,减少通过 field_name 遍历 Fields 的耗时 */
void SortFields();
/* 更新/插入单个字段值,插入后会进行 Fields 排序,减少通过 field_name 遍历 Fields 的耗时 */
void UpdateField(const std::string& field_name, const std::string& field_value);
void UpdateField(const Field& field);
/* 更新/插入多个字段值 */
void UpdateFields(const std::vector<std::string>& field_names, const std::vector<std::string>& field_values);
void UpdateFields(const FieldArray& fields);
/* 删除单个字段 */
void DeleteField(const std::string& field_name);
/* 删除多个字段 */
void DeleteFields(const std::vector<std::string>& field_names);
/* 序列化 Field 或 FieldArray 为 value 字符串 */
/* static 修饰的函数序列化/反序列化无需访问一个 Fields 对象的 fields_ */
static std::string SerializeValue(const FieldArray& fields);
static std::string SerializeValue(const Field& field);
std::string SerializeValue() const;
/* 反序列化 value 字符串为 Fields */
static Fields ParseValue(const std::string& value_str);
/* 获取字段 */
Field GetField(const std::string& field_name) const;
/* 检查字段是否存在 */
bool HasField(const std::string& field_name) const;
/* 重载运算符 [] 用于访问字段值 */
std::string operator[](const std::string& field_name) const;
/* 重载运算符 [] 用于修改字段值 */
std::string& operator[](const std::string& field_name);
/* 通过若干个字段查询 Key */
static std::vector<std::string> FindKeysByFields(leveldb::DB* db, const FieldArray& fields);
};
大 value 的 key 对应的 value 存储位置:VPtr
struct VPtr {
int fileno; // VLog 文件号
uint64_t offset; // 偏移量
};
class VLog
class VLog {
private:
// 该 VLog 是否活跃,即可插值
bool activate_;
// 最大 VLog 大小
std::size_t maxSize_;
// GC 计数器
std::size_t deadkeyCount;
public:
// 构造函数,默认赋值 GC 计数器为 GC 触发的最大阈值
VLog(bool activate, std::size_t maxSize, std::size_t gcThreshold)
: activate_(activate), maxSize_(maxSize), deadkeyCount(gcThreshold) {}
// 向 VLog 中添加一个新的键值对
virtual void append(const std::string& key, const std::string& value) = 0;
// 查找给定键对应的值
virtual VPtr lookup(const std::string& key) const = 0;
// 执行垃圾回收操作
virtual void GarbageCollection() = 0;
virtual ~VLog() {}
};
FieldArray、反序列化解析 value ;)VPtr 找到 VLog 中正确的 value;VPtr。| 功能 | 预计完成日期 | 分工 |
|---|---|---|
| Fields 类和相关接口实现 | 12月3日 | 朱维清、谷杰 |
| 测试实现字段功能 | 12月5日 | 谷杰 |
| VLog 类和相关接口实现 | 12月15日 | 朱维清、谷杰 |
| 测试实现 KV 分离 | 12月19日 | 朱维清 |
| Benchmark 测试(吞吐量、写放大、点查范围查) | 12月26日 | 谷杰 |
| Benchmark 测试(GC 开销、大小数据集、并发) | 12月26日 | 朱维清 |