姚凯文 aa4044fd8f | 3 weeks ago | ||
---|---|---|---|
Report | 删除 | 1 month ago | |
benchmarks | 删除 | 1 month ago | |
cmake | 删除 | 1 month ago | |
db | 删除 | 1 month ago | |
doc | 删除 | 1 month ago | |
helpers/memenv | 删除 | 1 month ago | |
include/leveldb | 删除 | 1 month ago | |
issues | 删除 | 1 month ago | |
port | 删除 | 1 month ago | |
table | 删除 | 1 month ago | |
test | 删除 | 1 month ago | |
third_party | 删除 | 1 month ago | |
util | 删除 | 1 month ago | |
.clang-format | 1 month ago | ||
.gitignore | 1 month ago | ||
.gitmodules | 1 month ago | ||
AUTHORS | 1 month ago | ||
CMakeLists.txt | 1 month ago | ||
CONTRIBUTING.md | 1 month ago | ||
LICENSE | 1 month ago | ||
NEWS | 1 month ago | ||
README.md | 3 weeks ago | ||
TODO | 1 month ago |
首先,我们需要将 value
扩展为一个字段数组,每个字段包含字段名和字段值。在 LevelDB
中存储时,可以将这些字段序列化为一个字符串形式(例如使用 key=value
的格式),然后将整个字段数组序列化为一个大的字符串。
不能采用直接在字段间加入转义字符的方法。如果原始字段值中本来就包含了转义字符(例如 \;
或 \=
),我们不能简单地通过单纯的转义机制来处理,因为这会导致循环转义的问题。
最终方案如下:
二进制序列化: 如果字段值本身可能包含复杂的字符(包括转义字符、分隔符等),可以考虑使用一种二进制序列化格式。二进制格式通常不会受到字符集和转义的困扰。
使用 C++ 标准库中的 std::ostringstream
和 std::istringstream
来序列化和反序列化数据。二进制序列化的格式是:
设计目标: 本设计旨在为特定字段或属性建立索引,从而提高对该字段查询的效率。在设计中,需要考虑以下几个方面:
实现思路: 为了实现这一目标,我们将数据存储和索引存储进行隔离,避免不同操作之间产生耦合问题。具体的实现思路如下:
我们会对两种数据创建两个数据库对象:kvDb
用于存储原始数据,indexDb
用于存储索引数据。FieldDb
作为外部接口层,负责管理这两个数据库对象,并提供原本的 LevelDB 接口以及新增加的索引相关功能。
class FieldDb {
Db *kvDb; // 原始数据存储
Db *indexDb; // 索引数据存储
std::vector<std::string> fieldWithIndex; // 存储已经创建索引的字段
std::queue<std::pair<bool, std::string>> taskQueue; // 存储创建或删除索引的任务
// 通过索引字段创建索引
bool CreateIndexOnField(const std::string& fieldName);
// 删除索引
bool DeleteIndex(const std::string& fieldName);
// 根据索引查询
std::vector<std::string> QueryByIndex(const std::string& fieldName);
};
kvDb
保持原始数据的一致性,不涉及任何索引相关操作。indexDb
中的 key 由原始 key 和索引字段联合编码而成,value 则为空。通过该设计,我们避免了将索引数据和原数据混合存储。// 示例:索引数据库的存储形式
// 例如,索引字段为 "author",原始 key 为 "book123" 时,索引的 key 为 "author|book123"。
每当我们需要创建或删除一个索引时,都会将相应的任务加入到 taskQueue
中。任务会在后台顺序执行,使用写锁来确保数据一致性。当一个任务完成后,会唤醒下一个任务继续执行。
创建索引:首先对原数据(kvDb
)中指定字段的所有记录进行提取,然后将每一条记录的索引字段与原 key 结合形成新的索引 key,并将该 key 存入 indexDb
。完成后,将字段名添加到 fieldWithIndex
中。
删除索引:删除某个字段的索引时,会从 indexDb
中移除相应的索引记录,并从 fieldWithIndex
中移除该字段名。
为了保证两个数据库间的一致性,所有的索引操作(包括创建和删除)都会通过 kvDb
的日志模块来管理。日志中包含两种操作类型:
在恢复数据时,需要解析日志并将数据恢复到相应的数据库中。通过统一的日志模块,我们确保了数据一致性。
FieldDb
对外提供了与 LevelDB 类似的接口,同时加入了索引相关功能的操作。这些接口包括:
CreateIndexOnField
:用于创建索引DeleteIndex
:用于删除索引QueryByIndex
:用于查询索引此外,FieldDb
还提供了 put
, get
, delete
等常规接口,用于操作原始数据。
bool FieldDb::CreateIndexOnField(const std::string& fieldName) {
// 锁住写锁
// 从 kvDb 获取该字段的所有记录
// 对每一条记录生成索引 key,并存入 indexDb
// 在 fieldWithIndex 中记录已创建索引的字段
// 释放写锁
}
bool FieldDb::DeleteIndex(const std::string& fieldName) {
// 锁住写锁
// 从 indexDb 删除该字段的所有索引记录
// 从 fieldWithIndex 中删除该字段
// 释放写锁
}
std::vector<std::string> FieldDb::QueryByIndex(const std::string& fieldName) {
// 检查该字段是否有索引
// 从 indexDb 获取索引数据并解码返回
}
通过将原数据和索引数据分别存储在两个独立的数据库中,并使用任务队列来顺序执行索引创建和删除操作,我们能够保证数据的隔离性、并发性和一致性。同时,通过 kvDb
统一管理日志,我们避免了索引操作与原数据操作之间的冲突。在设计上,我们尽量简化了模块之间的耦合,确保了系统的可扩展性和稳定性。