Redis通信协议(RESP)入门
Redis客户端和服务器端采用RESP(Redis Serialization Protocal)进行通信。RESP协议有以下特点:
- 二进制安全;
- 以行作为单位,客户端和服务器发送的命令或数据一律以
\r\n
(CRLF)作为换行符; - 第一个字节作为数据格式标记;
二进制安全是指允许协议中出现任意字符而不会导致故障。比如 C 语言的字符串以 \0 作为结尾不允许字符串中间出现\0, 而 Go 语言的 string 则允许出现 \0,我们说 Go 语言的 string 是二进制安全的,而 C 语言字符串不是二进制安全的。
RESP 定义了5种格式, 通过第一个字符来表示格式:
- 简单字符串(单行字符串, Simple String): 服务器用来返回简单的结果,比如"OK"。非二进制安全,且不允许换行。
- 错误信息(Error): 服务器用来返回简单的错误信息,比如"ERR Invalid Synatx"。非二进制安全,且不允许换行。
- 整数(Integer): llen、scard 等命令的返回值, 64位有符号整数。
- 字符串(多行字符串,Bulk String): 二进制安全字符串, 比如 get 等命令的返回值。
- 数组(Array, 又称 Multi Bulk Strings): Bulk String 数组,客户端发送指令以及 lrange 等命令响应的格式。
# 简单字符串(Simple String)
Simple Strings用于服务端对一些客户端命令的响应
格式:"+"开头 "\r\n"结尾,中间内容不能含有'\r'或'\n'
# set命令得到响应
127.0.0.1:6379> set name Foo
OK
# 服务端返回完整内容:"+OK\r\n“
# 退出命令
127.0.0.1:6379> quit
# 服务端返回的完整内容:+OK\r\n
2
3
4
5
6
7
Simple Strings的结构是非常简单的,而且不是二进制安全的;所以只会在服务端的简单内容返回。只是GET、MGET等命令的内容返回会用Buik Strings。
# 错误信息(Error)
Errors 用于服务端对客户端一些错误命令的返回内容
格式:"-"开头 "\r\n"结尾
# 执行不存在的命令
127.0.0.1:6379> gee name
(error) ERR unknown command 'gee'
# 服务端返回完整内容:"-ERR unknown command 'gee'\r\n"
# 错误的类型操作
127.0.0.1:6379> set name Bob
OK
127.0.0.1:6379> zadd name 1 Foo
(error) WRONGTYPE Operation against a key holding the wrong kind of value
# 服务端返回完成内容:"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
2
3
4
5
6
7
8
9
10
从例子中可以看出Erorrs响应中间部分的内容由两部分构成:大写代表错误类型,消息的表示错误详情。即-Error message\r\n
# 整数(Integer)
用于需要返回整数内容的命令,比如:INCR、LLEN等
格式:":"开头,\r\n结尾,中间部分必须是有效的整数
# incr
127.0.0.1:6379> set count 1
OK
127.0.0.1:6379> incr count
(integer) 2
# 服务端返回完整内容:":2\r\n"
# lpush llen del
127.0.0.1:6379> lpush userList Bob
(integer) 1
127.0.0.1:6379> llen userList
(integer) 1
# 服务端返回完整内容:":1\r\n",代表列表中当前元素书,数量
# del
127.0.0.1:6379> del userList
(integer) 1
# 服务端返回完整内容:":1\r\n", 含义是执行成功
# exists
127.0.0.1:6379> set name Bob
OK
127.0.0.1:6379> exists name2
(integer) 0
# 服务端返回完整内容:":0\r\n",含义是不存在
127.0.0.1:6379> exists name
(integer) 1
# 服务端返回完整内容:":1\r\n",含义是存在
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这些命令的响应内容都是使用Integers:SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD
# 字符串(Bulk String)
用来传输二进制安全的字符内容,最大长度是512MB
格式:'$'开头,后跟整数代表真正字符长度,然后\r\n,后面接真正的字符串内容,最后\r\n结尾,例:"$6\r\nfoobar\r\n"
正常返回
127.0.0.1:6379> set name foo
OK
127.0.0.1:6379> get name
"foo"
# 服务端返回完整内容:"$3\r\nfoo\r\n"
2
3
4
5
空字符串返回
127.0.0.1:6379> set name ""
OK
127.0.0.1:6379> get name
""
# 服务端返回完整内容:"$0\r\n\r\n"
2
3
4
5
null返回,即get不存在的key
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
# 服务端返回完整内容:"$-1\r\n"
2
3
4
5
当服务器返回Null Bulk Strings时,客户端库应该转换为Null对象返回,而不要返回空字符。空字符串和nil是两个含义,空字符串代表键的值为'',nil代表键不存在。
# Bulk String 数组
用作客户端向服务端发送命令时的内容格式,也用于服务端对一些命令的内容格式,如:LRANGE
格式:"*"开头,后跟整数表示数组长度,然后\r\n,后面是数组中的每个元素,他们可以是任意RESP类型(上面提到的Intergers、Bulk String等,格式如上),他们之间用\r\n分割,结尾\r\n
来看看set name Foo这条命令,client是怎么传给server的
127.0.0.1:6379> set name Foo
OK
# 客户端发的完整内容"*3\r\n$3\r\nset\r\n$4\r\nname\r\n$3\r\nFoo\r\n"
2
3
字段 | 解析 |
---|---|
*3 | 数组长度为3 |
\r\n | 分割符 |
$3 | 第一个字符串长度为3 |
\r\n | 分割符 |
set | 具体内容set |
\r\n | 分割符 |
$4 | 第二个字符串长度为4 |
\r\n | 分割符 |
name | 具体内容name |
\r\n | 分割符 |
$3 | 第三个字符串长度为3 |
\r\n | 分割符 |
Foo | 具体内容Foo |
\r\n | 结尾 |
接着看看mget name name1这条命令发出后,server返回的内容
127.0.0.1:6379> mget name name2
1) "Foo"
2) (nil)
# 服务端返回完成内容:*2\r\n$3\r\nFoo\r\n$-1\r\n"
2
3
4
字段 | 解析 |
---|---|
*2 | 数组长度为2 |
\r\n | 分割符 |
$3 | 第一个字符串长度为3 |
\r\n | 分割符 |
Foo | 具体内容Foo,即刚才set name Foo命令生效结果 |
\r\n | 分割符 |
$-1 | 第二个字符串长度为-1,表示nil,不存在 |
\r\n | 结尾 |
null数组表示
127.0.0.1:6379> blpop foo 1
(nil)
# 服务端返回完成内容: "*-1\r\n"
同样,客户端库也要区别null数组和空数组的区别。
2
3
4