「Kvrocks: 一款开源的企业级磁盘 KV 存储服务」对 Kvrocks 进行了整体性的介绍,本文从关键设计和内部实现来分析,希望对于想知道如何实现磁盘类型 Redis,以及想熟悉 Kvorcks 设计和实现的人带来一些帮助。
内部设计
Kvrocks 在内部设计上主要拆分成几个部分:
Redis 协议接收和解析模块,负责解析网络请求和解析 Redis 协议,相比 Redis 来说,Kvrocks 在 IO 处理以及命令执行都是多线程模型;
数据结构转换模块,负责将 Redis 复杂类型转为 RocksDB 可处理的简单 KV,不同类型在设计上会有一些小差异;
数据存储模块,Kvrocks 底层使用 RocksDB 并对其做了不少针对性的性能优化,文章「Kvrocks 在 RocksDB 上的优化实践」进行了详细说明,感兴趣的同学可以前往阅读;
主从复制模块,类似 Redis 的异步复制的方式,每个从库都会创建一个对应的复制线程。在实现方面,使用 RocksDB CheckPoint + WAL 来实现全量和增量同步;
集群模块,包含 Redis 集群协议兼容以及在线迁移的功能。这部分没有在上面的架构图中体现,感兴趣可以参考: 「Kvrocks 集群方案简介」。
下图为 Kvrocks 的整体设计示意图:
除此之外,代码里面还有一些后台线程 (Compaction Checker) 、任务的线程池以及统计功能由于篇幅关系没有体现。Redis 协议解析
Kvrocks 目前支持的还是 RESP 2 的协议,请求协议解析的相关代码都在 src/redis_request.cc
这个代码文件里面。相比于 Redis 的实现,Kvrocks 并没有自己实现接收和发送网络包逻辑,而直接使用比较成熟 Libevent 网络库,主要的原因多线程场景下,Libevent 的性能已经足够好,瓶颈主要在磁盘 IO, 没必要自己再造轮子。
解析请求的核心代码只是一个几十行代码的状态机,简化后的代码如下:
Status Request::Tokenize(evbuffer *input) {
...
while (true) {
switch (state_) {
case ArrayLen: // 读取协议的元素个数
line = evbuffer_readln(input, &len, EVBUFFER_EOL_CRLF_STRICT);
if (line[0] == '*') {
multi_bulk_len_ = std::stoull(std::string(line + 1, len-1));
state_ = BulkLen;
}
...
break;
case BulkLen: // 读取元素长度
line = evbuffer_readln(input, &len, EVBUFFER_EOL_CRLF_STRICT);
bulk_len_ = std::stoull(std::string(line + 1, len-1));
state_ = BulkData;
break;
case BulkData: // 读取元素数据
char *data = evbuffe