正式开始
实现并验证协议层
- 先创建一个项目:cargo new kv --lib
- 在Cargo.toml中添加依赖
- 在根目录下定义abi.proto
- 在根目录下创建build.rs,对构建过程进行自定义
- 在根目录下创建 examples,写一些代码测试客户端和服务器之间的协议
实现并验证 Storage trait
- 支持并发的 HashMap,可以使用 dashmap 创建一个 MemTable 结构,来实现 Storage trait
- 现在 src/storage/memory.rs 还没有被添加,所以 cargo 并不会编译它。要在 src/storage/mod.rs 开头添加代码
实现并验证 CommandService trait
最后的拼图:Service 结构的实现
/// Service 数据结构
pub struct Service<Store = MemTable> {
inner: Arc<ServiceInner<Store>>,
}
impl<Store> Clone for Service<Store> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
/// Service 内部数据结构
pub struct ServiceInner<Store> {
store: Store,
}
impl<Store: Storage> Service<Store> {
pub fn new(store: Store) -> Self {
Self {
inner: Arc::new(ServiceInner { store }),
}
}
pub fn execute(&self, cmd: CommandRequest) -> CommandResponse {
debug!("Got request: {:?}", cmd);
// TODO: 发送 on_received 事件
let res = dispatch(cmd, &self.inner.store);
debug!("Executed response: {:?}", res);
// TODO: 发送 on_executed 事件
res
}
}
// 从 Request 中得到 Response,目前处理 HGET/HGETALL/HSET
pub fn dispatch(cmd: CommandRequest, store: &impl Storage) -> CommandResponse {
match cmd.request_data {
Some(RequestData::Hget(param)) => param.execute(store),
Some(RequestData::Hgetall(param)) => param.execute(store),
Some(RequestData::Hset(param)) => param.execute(store),
None => KvError::InvalidCommand("Request has no data".into()).into(),
_ => KvError::Internal("Not implemented".into()).into(),
}
}
- Service 结构内部有一个 ServiceInner 存放实际的数据结构,Service 只是用 Arc 包裹了 ServiceInner。这也是 Rust 的一个惯例,把需要在多线程下 clone 的主体和其内部结构分开,这样代码逻辑更加清晰
- execute() 方法目前就是调用了 dispatch,但它未来潜在可以做一些事件分发。这样处理体现了 SRP(Single Responsibility Principle)原则
- dispatch 其实就是把测试代码的 dispatch 逻辑移动过来改动了一下
重点
- 在 Rust 下,但凡出现两个数据结构 v1 到 v2 的转换,你都可以先以 v1.into() 来表示这个逻辑,继续往下写代码,之后再去补 From 的实现
- 如果 v1 和 v2 都不是你定义的数据结构,那么你需要把其中之一用 struct 包装一下,来绕过之前孤儿规则。
- 要特别注意:测试代码要围绕着系统稳定的部分,也就是接口,来测试,而尽可能少地测试实现
- Dashmap 内部封装了一个优化的 Arc<RwLock<HashMap>>
小结
一个有潜在生产环境质量的 Rust 项目应该如何开发
-
要对需求有一个清晰的把握,找出其中不稳定的部分(variant)和比较稳定的部分(invariant)
在 KV server 中,不稳定的部分是,对各种新的命令的支持,以及对不同的 storage 的支持。所以需要构建接口来消弭不稳定的因素,让不稳定的部分可以用一种稳定的方式来管理
-
代码和测试可以围绕着接口螺旋前进,使用 TDD 可以帮助我们进行这种螺旋式的迭代。
在一个设计良好的系统中:接口是稳定的,测试接口的代码是稳定的,实现可以是不稳定的
好用链接
build.rs prost_build thiserror定义错误现在 src/storage/memory.rs 还没有被添加,所以 cargo 并不会编译它。要在 src/storage/mod.rs 开头添加代码
精选问答
-
dev-dependencies 与 dependencies的第三方crate是如何划分的?
a. examples / test 里用的库是 dev-dependencies + dependencies
b. build.rs 里用到的库是 build-dependencies + dependencies
c. 正常代码(库/二进制)用的是 dependencies
-
tcp的半包粘包等,是被prost处理掉了吗?
a. prost 只是 protobuf serialize / deserialize 的工具,并不负责做这样的事情
b. 半包粘包的问题,是被 async_prost 处理的:github.com/tyrchen/asy…