serde你在干什么 - 实践篇

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

1
"+OK\r\n"

Errors

1
"-Error message\r\n"

​ 和Simple Strings基本相同,但是添加一个错误类型段

Integers

1
":1000\r\n"

​ bool值也使用0、1整型表示

Bulk Strings

1
2
"$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。

1
2
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

1
2
3
4
5
6
7
8
9
10
11
$ 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 的格式告知 命令及参数;但是服务端反馈结果给客户端时,可以使用任意的格式组合。这其实表示了有两套序列化和反序列化的接口:

  1. 客户端的序列化,服务端的反序列化,以 Array of Bulk Strings 为中介
  2. 服务端的序列化,客户端的反序列化,以 任意的格式组合 为中介

当前实践篇,主要目的还是学习,简化一下,暂时忽略第二种,姑且认为服务端给客户端发送的也是Array of Bulk Strings,类型由rust本身的类型系统进行约束。

因此,我们希望客户端和服务端都有一套共同的Request/Response类型。还是以GetSetRemove三兄弟为例来设计。

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序列表示,所以,假定序列化接口为

1
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

在服务端一侧,数据是从网络连接中读取的,因此我们假定反序列化接口为,

1
2
3
4
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`"),
}
// Remove 略
}

对于GetResponse等结构,也是基本相同的过程,只不过由服务端序列化后,交给客户端去反序列化。

有了基本的测试用例,就可以着手具体实现了,还是分为Serialize和Deserialize两个过程。