serde 源码解析 | 用Redis Simple Protocol做练习
serde你在干什么
的实践篇。基于serde框架,我们来写一个redis协议的deserializer和serializer
Redis Protocol
overview
redis 客户端和服务端通信的序列化标准
优点(哪都有这三个):
- Simple to implement.
- Fast to parse.
- Human readable.
特点:
- 对bulk数据,前置数据长度,binary safe,不需要考虑处理转义的事情。
- 为Error专门设置了一个类型,方便客户端的错误处理。
序列化规范
可以序列化的类型:
- For Simple Strings the first byte of the reply is “+”
- For Errors the first byte of the reply is “-“
- For Integers the first byte of the reply is “:”
- For Bulk Strings the first byte of the reply is “$” ,等效于&[u8]
- For Arrays the first byte of the reply is “
*
“,即Vec
Simple Strings
Errors
和Simple Strings基本相同,但是添加一个错误类型段
Integers
bool值也使用0、1整型表示
Bulk Strings
| "$6\r\nfoobar\r\n" [u8; 6] "$-1\r\n" () None
|
Arrays
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| "*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n" [b"foo", b"bar"] "*3\r\n:1\r\n:2\r\n:3\r\n" [1, 2, 3] "*0\r\n" []
*2\r\n *3\r\n :1\r\n :2\r\n :3\r\n *2\r\n +Foo\r\n -Bar\r\n [[1, 2, 3], ["Foo", Err(Bar)]]
*3\r\n $3\r\n foo\r\n $-1\r\n $3\r\n bar\r\n ["foo",nil,"bar"]
|
使用样例
使用上,客户端发送 用 Array of Bulk Strings 的形式给服务器端发送 命令及参数 作为 Request;服务器端给客户端发送 任意的类型(组合) 作为 Response。
| C: *2\r\n$3\r\nget\r\n$4\r\nkey1\r\n get key1 S: $3\r\n101\rn 101
|
redis 支持多个command的提交,一次性运行,提升单位时间的执行数量。成为piplining
| $ printf '*2\r\n$3\r\nget\r\n$1\r\nA\r\n*2\r\n$3\r\nget\r\n$1\r\nA\r\n' | nc localhost 6379 $3 101 $3 101
$ printf 'get A\n get A\n' | nc localhost 6379 $3 101 $3 101
|
serde实现
如前所述,正常情况下,客户端 –> 服务端, 得用 Array of Bulk Strings 的格式告知 命令及参数;但是服务端反馈结果给客户端时,可以使用任意的格式组合。这其实表示了有两套序列化和反序列化的接口:
- 客户端的序列化,服务端的反序列化,以 Array of Bulk Strings 为中介
- 服务端的序列化,客户端的反序列化,以 任意的格式组合 为中介
当前实践篇,主要目的还是学习,简化一下,暂时忽略第二种,姑且认为服务端给客户端发送的也是Array of Bulk Strings,类型由rust本身的类型系统进行约束。
因此,我们希望客户端和服务端都有一套共同的Request/Response类型。还是以Get
、Set
、Remove
三兄弟为例来设计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #[derive(Debug, Serialize, Deserialize)] enum Request { Get { key: String }, Set { key: String, value: String }, Remove { key: String }, }
#[derive(Debug, Serialize, Deserialize)] pub enum GetResponse { Ok(Option<String>), Err(String), }
#[derive(Debug, Serialize, Deserialize)] pub enum SetResponse { Ok(()), Err(String), }
#[derive(Debug, Serialize, Deserialize)] pub enum RemoveResponse { Ok(()), Err(String), }
|
根据协议要求,服务端接受的一定是 Array of Bulk Strings,用u8
序列表示,所以,假定序列化接口为
| pub fn<T: Serialize> to_bytes(value: &T) -> Result<Vec<u8>>
|
序列化过程可以通过如下的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #[test] fn test_ser() { let get = Request::Get { key: "key1".to_owned(), }; assert_eq!( to_bytes(&get).unwrap(), b"*2\r\n$3\r\nGet\r\n$4\r\nkey1\r\n" ); let set = Request::Set { key: "key1".to_owned(), value: "value1".to_owned(), }; assert_eq!( to_bytes(&set).unwrap(), "*3\r\n$3\r\nSet\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n".as_bytes() ); let rm = Request::Remove { key: "key1".to_owned(), }; assert_eq!( to_bytes(&rm).unwrap(), b"*2\r\n$6\r\nRemove\r\n$4\r\nkey1\r\n" ); }
|
在这个过程中,enum的名字不重要,每个variant的名字才是”命令”,所以放入array的第一项,variant的数据字段,追加到array中作为命令的参数。
影响序列化的关键一点是,不管”参数”是什么类型,最后都会以bulk string的形式作为array的元素。即使数据本身是int型,比如42,也会序列化为$2\r\n\42\r\n
,而不是:42\r\n
。
在服务端一侧,数据是从网络连接中读取的,因此我们假定反序列化接口为,
| pub fn from_reader(r: R) -> Result<T> where R: io:Read, T: DeserializeOwned
|
反序列化过程可以通过如下的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #[test] fn test_de() { let get = "*2\r\n$3\r\nGet\r\n$4\r\nkey1\r\n".as_bytes(); match from_reader::<_, Request>(get).unwrap() { Request::Get { key } => assert_eq!(key, "key1".to_owned()), _ => assert!(false, "fail to deserialize into `Get`"), }
let set = "*3\r\n$3\r\nSet\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n".as_bytes(); match from_reader::<_, Request>(set).unwrap() { Request::Set { key, value } => { assert_eq!(key, "key1".to_owned()); assert_eq!(value, "value1".to_owned()); } _ => assert!(false, "fail to deserialize into `Set`"), } }
|
对于GetResponse
等结构,也是基本相同的过程,只不过由服务端序列化后,交给客户端去反序列化。
有了基本的测试用例,就可以着手具体实现了,还是分为Serialize和Deserialize两个过程。