陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

22|阶段实操(2):构建一个简单的KV server-基本流程

使用线程池和哈希分派来处理并发
实现 MemTable 的 get_iter() 方法
为剩下 6 个命令构建测试,并实现它们
代码基本满足 OCP 原则
使用接口进行交互
几乎不需要注释
代码可读性强
接口是稳定的,测试接口的代码是稳定的,实现可以是不稳定的
使用 TDD 进行螺旋式迭代
代码和测试围绕接口螺旋前进
构建接口来管理不稳定的部分
找出不稳定的部分和稳定的部分
对需求进行清晰把握
延伸思考
思考题
最佳实践
TDD
需求把握
KV Server 开发实践

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
上篇我们的 KV store 刚开了个头,写好了基本的接口。你是不是摩拳擦掌准备开始写具体实现的代码了?别着急,当定义好接口后,先不忙实现,在撰写更多代码前,我们可以从一个使用者的角度来体验接口如何使用、是否好用,反观设计有哪些地方有待完善。
还是按照上一讲定义接口的顺序来一个一个测试:首先我们来构建协议层。

实现并验证协议层

先创建一个项目:cargo new kv --lib。进入到项目目录,在 Cargo.toml 中添加依赖:
[package]
name = "kv"
version = "0.1.0"
edition = "2018"
[dependencies]
bytes = "1" # 高效处理网络 buffer 的库
prost = "0.8" # 处理 protobuf 的代码
tracing = "0.1" # 日志处理
[dev-dependencies]
anyhow = "1" # 错误处理
async-prost = "0.2.1" # 支持把 protobuf 封装成 TCP frame
futures = "0.3" # 提供 Stream trait
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "macros", "net" ] } # 异步网络库
tracing-subscriber = "0.2" # 日志处理
[build-dependencies]
prost-build = "0.8" # 编译 protobuf
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文详细介绍了使用Rust构建一个简单的KV server的过程,主要涉及了协议层的实现和验证。作者通过实际的代码示例,演示了构建KV server的基本流程,包括创建项目、编写protobuf代码、实现基本的protobuf接口、创建客户端和服务器示例代码、以及网络和异步处理的内容。文章强调了需求的稳定性和测试的重要性,以及如何围绕接口进行代码和测试的迭代开发。此外,文章还提到了使用线程池来处理并发的思考,展示了对系统性能优化的思考。整体而言,本文为读者提供了一份清晰的指南,展示了如何在Rust下撰写高质量的代码,并体现了Rust语言的优雅和先进性。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(26)

  • 最新
  • 精选
  • newzai
    dev-dependencies 与 dependencies的第三方crate是如何划分的?tokio 为啥不放到 dependencies? 放到 dependencies 与 dev-dependencies 有啥区别?某些如何决策一个 crate放到哪个 dependencies?

    作者回复: examples / test 里用的库是 dev-dependencies + dependencies。build.rs 里用到的库是 build-dependencies + dependencies。正常代码(库/二进制)用的是 dependencies

    2021-10-11
    3
    13
  • Geek_b52974
    为何不是这样设计 fn set(&self, table: &str, key: String, value: impl Into<Value>) 这样以来就可以让使用者知道他有一个新的type 需要存时应该 implement 这个 trait 也不会让使用时需要一直 写into

    作者回复: 嗯,没问题,可以: fn set( &self, table: &str, key: impl Into<String>, value: impl Into<Value>, ) -> Result<Option<Value>, KvError>;

    2021-11-06
    7
  • 施泰博
    用powershell的。RUST _LOG=info。改成$env:RUST_LOG="info";然后再cargo run

    作者回复: 👍 很好的 tips

    2021-12-21
    6
  • Roy Liang
    老师,get_all接口为什么不好?

    作者回复: get_all() 会有太多内存占用,所以一般需要提供可遍历的结果时,倾向于使用 iterator

    2021-10-20
    3
  • losuika
    感觉 get_iter 加上生命周期的约束好一些,因为现在 GAT 还没稳定,可以这样实现下, fn get_iter<'a>( &'a self, table: &str, ) -> Result<Box<dyn Iterator<Item = crate::Kvpair> + 'a>, crate::KvError> { let table = self.get_or_create_table(table); let inner = Iter::new(table); Ok(Box::new(inner)) } struct Iter<'a> { _table: *const Ref<'static, String, DashMap<String, Value>>, inner: dashmap::iter::Iter<'a, String, Value>, } impl<'a> Iter<'a> { fn new(table: Ref<'a, String, DashMap<String, Value>>) -> Self { let t = unsafe {std::mem::transmute::<Ref<'a, String, DashMap<String, Value>>, Ref<'static, String, DashMap<String, Value>>>(table)}; let _table = Box::leak(Box::new(t)) as *const Ref<'static, String, DashMap<String, Value>>; let inner = unsafe {(*_table).iter()}; Self {_table, inner} } } impl<'a> Iterator for Iter<'a> { type Item = crate::Kvpair; fn next(&mut self) -> Option<Self::Item> { let item = self.inner.next(); item.map(|v| crate::Kvpair::new(v.key(), v.value().clone())) } } impl<'a> Drop for Iter<'a> { fn drop(&mut self) { unsafe { drop_in_place(self._table as *mut Ref<'a, String, DashMap<String, Value>>) }; } }

    作者回复: 👍

    2021-11-29
    2
  • 罗杰
    必须仔细看老师的教程,不仔细就掉坑里了。说实话老师的文章讲的是真心详细,基本上把所有的坑都讲了。如果实在编译不过,去下载老师的源码,千万记得要坚持下去。

    作者回复: 谢谢!

    2021-10-24
    2
  • xl000
    ```Rust impl Kvpair { /// 创建一个新的 kv pair fn new(key: impl Into<String>, value: impl Into<Value>) -> Self { Self { key: key.into(), value: Some(value.into()), } } } ``` 老师Value类型的参数为什么不用impl Into<Value>来定义呢,会有什么问题吗

    作者回复: 嗯,可以用 impl Into<Value>

    2021-10-15
    2
  • pedro
    得益于老师良好的抽象,我抽出了中午的时间,完成了 hdel 和 hexist 两个命令,如下: ``` running 6 tests test service::command_service::tests::hget_with_non_exist_key_should_return_404 ... ok test service::command_service::tests::hexist_should_work ... ok test service::command_service::tests::hget_should_work ... ok test service::command_service::tests::hdel_should_work ... ok test service::command_service::tests::hset_should_work ... ok test service::command_service::tests::hgetall_should_work ... ok ``` 有时间再来慢慢补充,rust 确实是爽的不行。

    作者回复: 👍

    2021-10-11
    2
    2
  • 我把prost的版本改成0.9(0.8版本的可以正常运行) ,然后再测试examples的时候 client.send的时候有个报错: the method `send` exists for struct `AsyncProstStream<tokio::net::TcpStream, CommandResponse, CommandRequest, AsyncDestination>`, but its trait bounds were not satisfied the following trait bounds were not satisfied: `AsyncProstStream<tokio::net::TcpStream, CommandResponse, CommandRequest, AsyncDestination>: futures::Sink<_>` which is required by `AsyncProstStream<tokio::net::TcpStream, CommandResponse, CommandRequest, AsyncDestination>: SinkExt<_>`rustcE0599 stream.rs(24, 1): doesn't satisfy `_: SinkExt<_>` stream.rs(24, 1): doesn't satisfy `_: futures::Sink<_>`. 我试着用“如何阅读源码”里边的方法,但是还是不太懂这个错误的原因。希望老师能指点下查错的方法

    作者回复: 你需要 async-prost 也升级到 prost 0.9 版本。注意像 prost 这样的库,在不同版本间生成的 protobuf 代码有可能不一样,所以你不能把 kv 中使用 0.9 编译出来的 protobuf,用在 0.8 版本中 encode/decode。就好比你要用前朝的尚方宝剑斩本朝的官一样。 我把 async-prost 升级了一下(0.3),也把 kv 升级到 prost 0.9,编译可以通过,你可以试试看。

    2021-11-09
    2
    1
  • qinsi
    tcp的半包粘包等,是被prost处理掉了吗?

    作者回复: prost 知识 protobuf serialize / deserialize 的工具,并不负责做这样的事情。你说的半包粘包的问题,是被 async_prost 处理的:https://github.com/tyrchen/async-prost/blob/master/src/reader.rs#L136。当然,也可以用网络那一堂讲到的 Frame / LengthDelimitedCodec 来姐姐。

    2021-10-26
    1
收起评论
显示
设置
留言
26
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部