一切网络通信,皆需要双方遵守协议才能互联。Redis协议在以下几点之间做出了折衷:
- 简单的实现
- 快速被计算机解析
- 简单到可被人工解析
网络层
Redis在TCP端口6379上监听到来的连接,客户端连接到来时,Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以rn
结尾。
请求
Redis接收由不同参数组成的命令。一旦收到命令,将会立刻被处理,并响应给客户端。
新的统一请求协议
新的统一协议在Redis 1.2中引入,在Redis 2.0中,成为与Redis服务器通讯的标准方式。
在这个统一协议里,发送给Redis服务端的所有参数都是二进制安全的。
如下是通用形式:
代码语言:javascript复制*<number of arguments> CR LF
$<number of bytes of argument 1> CR LF
<argument data> CR LF
...
$<number of bytes of argument N> CR LF
<argument data> CR LF
示例:
代码语言:javascript复制➜ ~ nc localhost 6379
keys *
*2
$18
user:sign:5:202101
$18
seckill_vouchers:6
上面的命令看上去像是单引号字符串,所以可在查询中看到每个字节的准确值:
代码语言:javascript复制"*2rn$18rnuser:sign:5:202101rn$18rnseckill_vouchers:6rn"
在Redis的响应中也使用这样的格式。批量回复时,这种格式用于每个参数。 实际的统一请求协议是Redis用于返回列表项,并调用 Multi-bulk回复。 仅仅是N个以*rn为前缀的不同批量回复,是紧随的参数(批量回复)数目。
响应
Redis用不同的响应类型回复命令。它可能从服务器发送的第一个字节开始校验回复类型:
- 单行回复
回复的第一个字节将是
set java edge
OK
- 错误消息
回复的第一个字节将是
-
keys*
-ERR unknown command `keys*`, with args beginning with:
- 整型数字
回复的第一个字节将是
:
- 批量回复
回复的第一个字节将是
$
keys *
*2
$18
user:sign:5:202101
$18
seckill_vouchers:6
- 多个批量回复
回复的第一个字节将是
*
Simple Strings
状态回复(或者单行回复)以“ ”开始以“rn”结尾的单行字符串形式。例如:
“ OKrn” 客户端库将在“ ”后面返回所有数据,正如上例中字符串“OK”一样。
Errors
错误回复发送类似于状态回复。唯一的不同是第一个字节用“-”代替“ ”。
错误回复仅仅在一些意料之外的事情发生时发送,例如:如果你试图执行一个操作来应付错误的数据类型,或者如果命令不存在等等。所以当收到一个错误回复时,客户端将会出现一个异常。
Integers
这种回复类型只是用CRLF结尾字符串来表示整型,用一个字节的“:”作为前缀。例如:“:0rn”,或者“:1000rn”是整型回复。
像INCR或者LASTAVE命令用整型回复作为实际回复值,此时对于返回的整型没有特殊的意思。它仅仅是为INCR、LASTSAVE的UNIX时间等增加数值。
一些命令像EXISTS将为true返回1,为false返回0。
其它命令像SADD、SREM和SETNX如果操作实际完成了的话将返回1,否则返回0。
接下来的命令将回复一个整型回复:SETNX、DEL、EXISTS、INCR、INCRBY、DECR、DECRBY、DBSIZE、LASTSAVE、RENAMENX、MOVE、LLEN、SADD、SREM、SISMEMBER、SCARD。
Bulk Strings
批量响应被服务器用于返回一个单二进制安全字符串。
代码语言:javascript复制C: GET mykey
S: $6rnfoobarrn
服务器发送第一行响应,该行以“$”开始后面跟随实际要发送的字节数,随后是CRLF,然后发送实际数据,随后是2个字节的额外数据用于最后的CRLF。服务器发送的准确序列如下:
代码语言:javascript复制"$6rnfoobarrn"
如果请求的值不存在,批量响应将使用特殊的值-1来作为数据长度,例如:
代码语言:javascript复制C: GET nonexistingkey
S: $-1
当请求的对象不存在时,客户端库API不会返回空字符串,而会返回空对象。例如:Ruby库返回‘nil’,而C库返回NULL(或者在回复的对象里设置指定的标志)等等。
Arrays
像命令LRNGE需要返回多个值(列表的每个元素是一个值,而LRANGE需要返回多于一个单元素)。使用多批量写是有技巧的,用一个初始行作为前缀来指示多少个批量写紧随其后。批量回复的第一个字节总是*,例如:
代码语言:javascript复制C: LRANGE mylist 0 3
s: *4
s: $3
s: foo
s: $3
s: bar
s: $5
s: Hello
s: $5
s: World
正如您可以看到的多批量回复是以完全相同的格式使用Redis统一协议将命令发送给服务器。
服务器发送的第一行是*4rn,用于指定紧随着4个批量回复。然后传送每个批量写。
如果指定的键不存在,则该键被认为是持有一个空的列表,且数值0被当作多批量计数值来发送,例如:
代码语言:javascript复制C: LRANGE nokey 0 1
S: *0
当BLPOP命令超时时,它返回nil多批量回复。这种类型多批量回复的计数器是-1,且值被当作nil来解释。例如:
代码语言:javascript复制C: BLPOP key 1
S: *-1
当这种情况发生时,客户端库API将返回空nil对象,且不是一个空列表。这必须有别于空列表和错误条件(例如:BLPOP命令的超时条件)。
多批量回复中的Nil元素 多批量回复的单元素长度可能是-1,为了发出信号这个元素被丢失且不是空字符串。这种情况发送在SORT命令时,此时使用GET模式选项且指定的键丢失。一个多批量回复包含一个空元素的例子如下:
代码语言:javascript复制S: *3
S: $3
S: foo
S: $-1
S: $3
S: bar
第二个元素是空。客户端库返回如下:
代码语言:javascript复制["foo",nil,"bar"]
多命令和管道
客户端能使用同样条件为了发出多个命令。管道用于支持多命令能够被客户端用单写操作来发送,它不需要为了发送下一条命令而读取服务器的回复。所有回复都能在最后被读出。
通常Redis服务器和客户端拥有非常快速的连接,所以在客户端的实现中支持这个特性不是那么重要,如果一个应用需要在短时间内发出大量的命令,管道仍然会非常快。
旧协议发送命令 在统一请求协议出现前,Redis用不同的协议发送命令,现在仍然支持,它简单通过手动telnet。在这种协议中,有两种类型的命令:
内联命令:简单命令其参数用空格分割字符串。非二进制安全。 批量命令:批量命令准确如内联命令,但是最后的参数用特殊方式来处理用于保证最后参数二进制安全。 内联命令 最简单的发送Redis命令的方式是通过内联命令。下面是一个使用内联命令聊天的服务器/客户端的例子(服务器聊天用S:开始,客户端聊天用C:开始)。
代码语言:javascript复制C: PING
S: PONG
下面是另外一个内联命令返回整数的例子:
代码语言:javascript复制C: EXISTS somekey
S: :0
因为‘somekey’不存在,所以服务器返回‘:0’。
注意:EXISTS命令带有一个参数。参数用空格分割。
批量命令
一些命令当用内联命令发送时需要一种特殊的格式用于支持最后参数二进制安全。这种命令用最后参数作为“字节计数器”,然后发送批量数据(因为服务器知道读取多少个字节,所以是二进制安全的)。
请看下面的例子:
代码语言:javascript复制C: SET mykey 6
C: foobar
S: OK
这条命令的最后一个参数是‘6’。这用于指定随后数据的字节数,即字符串“foobar”。注意:虽然这个字节流是以额外的两个CRLF字节结尾的。
所有批量命令都是用这种准确的格式:用随后数据的字节数代替最后一个参数,紧跟着后面是组成参数本身的字节和CRLF。为了更清楚程序,下面是通过客户端发送字符串的例子:
代码语言:javascript复制"SET mykey 6rnfoobarrn"
Redis有一个内部列表,用于表示哪些命令是内联,哪些命令是批量,所以你不得不发送相应的命令。强烈建议使用新的统一请求协议来代替老的协议。