携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情
今天仍然是实践课,目标是基本实现 KV server。让我们开始吧!
01-系统架构
在开始之前,我们先大概了解下课程中要构建的 kv server 的基本架构:
与 Redis 一样,kv server 是 C/S 架构的程序,使用 TCP 连接。Client 与 server 之间的协议通过 proto 定义。Disruptor 负责识别并将 client 发来的命令分发给各个命令处理单元。 Storage 负责键值的存储。
整个系统的接口可以分为三个部分:
- 客户端和服务器的接口或者说协议(proto,9个命令)
- 服务器和命令处理流程的接口(CommandService trait)
- 服务器和存储的接口(Storage trait)
其中第一部分在昨天的 example 示例中已经学习过了,今天的主要内容是其余的两部分。
02-CommandService
在当前版本里,proto 消息中总共定义了9种命令,而且将来非常有可能会扩大命令支持的范围。所以,对于命令处理,很有必要定义一个 trait 来规范命令处理的流程。CommandService trait 的定义如下:
pub trait CommandService {
// 参数 store 为实现了 Storage trait 的结构体引用,
// Storage trait 会在后面学习到,它封装了存储相关的一些接口
fn execute(self, store: &impl Storage) -> CommandResponse;
}
然后我们来看一个具体的命令(Hget)处理过程实现:
impl CommandService for Hget {
fn execute(self, store: &impl Storage) -> CommandResponse {
// 从 store 中取某个 table 中的某个 key
match store.get(&self.table, &self.key) {
Ok(Some(v)) => v.into(), // 如果从 table 中取得值
Ok(None) => KvError::NotFound(self.table, self.key).into(), // 如果 storage 中不包含值
Err(e) => e.into(), // 过程中遇到其他问题
}
}
}
课程示例代码只实现了 Hget / Hgetall / Hset 命令,其他的实现作为了课后作业。我们自己来实现一下 Hdel 命令(逻辑可能不太完善,无论 storage 中是否包含 key,只要最终结果是 storage 不包含 key 了即返回成功):
impl CommandService for Hdel {
fn execute(self, store: &impl Storage) -> CommandResponse {
match store.del(&self.table, &self.key) {
Ok(_) => CommandResponse { status: StatusCode::OK.as_u16() as _, ..Default::default() },
Err(v) => v.into(),
}
}
}
按照这个思路,如果我们后面要增加对新命令的支持,也只需要为其实现 CommandService trait 即可。新增的命令可以很平滑地融入之前的代码中。
03-Storage
在前一节的实现中,table 和 key 都是从某个实现了 Storage trait 的结构中获得。Storage trait 是对存储的抽象。
pub trait Storage {
// 从 table 中获取 key 对应的值,可能有、也可能没有、也可能报错,
// 所以返回值是 Result<Option<Value>, KvError>
fn get(&self, table: &str, key: &str) -> Result<Option<Value>, KvError>;
// 向 table 中插入 key : value 对
fn set(&self, table: &str, key: String, value: Value) -> Result<Option<Value>, KvError>;
// 判断 table 中是否包含某个 key
fn contains(&self, table: &str, key: &str) -> Result<bool, KvError>;
// 删除 table 中的某个 key:value 对,
// 返回值可能为有值(key 原来对应的 value)、可能为 None 表示不包含 key、可能为 Err
fn del(&self, table: &str, key: &str) -> Result<Option<Value>, KvError>;
// 返回所有的 kvpair
fn get_all(&self, table: &str) -> Result<Vec<Kvpair>, KvError>;
// 返回 kvpair 的迭代器
fn get_iter(&self, table: &str) -> Result<Box<dyn Iterator<Item = Kvpair>>, KvError>;
}
我们可以以内存作为存储介质,这样只需要为其实现 Storage trait,它就能与程序集成在一起;当那天需要持久化到磁盘时,可以以文件作为存储介质,再为其实现 Storage trait,程序就可以无缝切换到支持持久化的介质了。同样,如果要存到数据库中,也可以按照这个范式实现。
课程示例中定义了一个基于 DashMap 的数据结构,MemTable。
本节课程链接: 《21|阶段实操(1):构建一个简单的KV server-基本流程》 《22|阶段实操(2):构建一个简单的KV server-基本流程》