Redis Protocol specification
Redis 客户端使用RESP(Redis Serialization protocol)与Redis服务器通信。尽管此协议是为Redis而设计,但是它也能被当做其他client-server软件项目协议,RESP是以下一个方面的着重实现:
RESP能序列化不同数据类型,例如integer、strings、arrays。也为errors指定了特殊类型。作为代表执行命令参数的字符串数组,请求由客户端(Client)发出,发至Redis服务器端.。Redis服务器端返回客服端指定命令的数据类型。
RESP是binary-safe的,不需要处理由一个流程到另一个流程转换的bulk data,因为它用前缀长度转换bulk data(块数据)。
Note:上述协议仅被用于client-server通信。Redis集群用一个不同的二进制协议,以便节点之间交换数据。
网络层(Networking layer)
一个客户端连接到Redis服务器相当于创建了一个连接到6379端口的TCP链接。
虽然RESP技术上没有指定TCP,在Redis协议上下文中只使用TCP链接(或者等同的面向链接的流,例如Unix sockets)。
请求-响应模式(Request-Response model)
Redis接受由不同参数组成的命令。一旦命令没接受,将被处理,一个响应将被送至客户端。
这可能是最简单的模型,然而有两处例外:
- Redis支持pipeline。所以客户端可以一次发送多条命令,并等待服务器响应。
- 当Redis客户端注册到一个Pub/Sub 频道(channel)时,协议改变语义变成一个push协议,这意味着客户端不需要再发送命令因为服务器将要默认发送到客户端新消息(对于channel来说就是向订阅的此频道的客户端)
除了以上的两个例外,Redis协议是一个简单的请求响应协议。
RESP protocol descripton
RESP协议在Redis1.2中已经介绍,但是在Redis2.0协议中,他变成了与Redis服务器通信的标准方式。这是你应该在Redis客户端实现的协议。
RESP实际是支持以下数据类型的串行化协议:简单字符串,错误,整形,Bulk String,数组。
RESP作为请求-响应协议被用在Redis的方式如下:
- 客户端将命令作为一个Bulk Strings的RESP数组发送到Redis服务器
- 服务器根据the命令实现,使用RESP中的类型中的一个进行响应
对于RESP来说,一些数据的类型依靠第一个字节来确定:
- 简单字符串:the first byte:“+”
- Errors:the first byte:“_”
- 整形:the first byte:“:”
- Bulk Strings : the first byte:“$”
- 数组:the first byte:“*”
另外,RESP使用一个特殊种类的Bulk String或者随后指定的数组,能够响应NULL值。
对于RESP,协议的不同部分总是以“\r\n”(CRLF)终结。
RESP 简单字符串(Simple String)
简单字符串以下列方法被编码:一个“+”字符,紧接着一个不包含CR或者LF字符的字符串(不允许换行),然后以CRLF(this is “\r\n”)。(http://blog.csdn.net/lw370481/article/details/8229344)
简单字符串被用于传输没有二进制安全问题的字符串,而且用最小的代价。对于很多Redis命令的响应,只是简单使用“OK”标识,它被使用以下5个字节表示:
“+OK\r\n”
为了发送有二进制安全的字符串,RESP的Bulk Strings被使用。
当Redis使用简单的字符串响应时,客户端库应该能够返回给调用者一个 “+”号之后的不包含“\r\n”(CRLF)的字符。
RESP 错误(Errors)
RESP有一个表示错误的特殊数据类型。实际上,错误很像RESP的简单字符串,但是第一个字符(字节)是一个减号“-”而不是一个“+”号。在RESP中,错误与简单字符串之间
的不同体现在错误被客户端当做异常(Exceptions),而构成错误类型的字符串是错误信息本身。
基础的格式是:
“-Error message\r\n”
Error回应,只发生在一些错误发生的时候,举个例子,如果你尝试去执行错误的数据类型,或者命令不存在。当收到Error回应时,异常应该被抛出。
下列是Error回复的例子:
-ERR unknown command ‘foobar’
-WRONGTYPE Operation against a key holding the wrong kind of value
“-”后的第一个字到第一个空格或者新行,代表错误的返回类型。这仅是Redis所使用的协议或者约定,不是RESP错误格式的一部分。
举个例子,ERR是广义错误,而WRONGTYPE是一个更加具体的错误,指明客户端尝试去对错误的数据类型进行操作。这些被称作(ERR,WRONGTYPE)被称作Error prefix,
这种前缀是一种(允许客户端理解由服务器端返回的错误类型,而不必依靠给出的精确信息,精确信息往往随着时间变化)方式。
客户端实现也许为不同的错误返回不同的异常,或者直接通过提供错误的名称以字符串的形式给调用者,以这种一般方式trap(困,抓取)错误。
然而,这一个特性通常不被认为是至关重要的,因为很少用到。一个局限的客户端实现 可能直接返回一个泛化的错误情况,例如false。
RESP 整型(Integer)
这个类型仅仅是一个以CRLF结尾的字符串,代表一个整数,前缀是一个“:”字节。例如,“:0\r\n”,或者“:1000\r\n”是整型响应。
许多Redis命令返回RESP整型,例如INCR、LLEN和LASTSAVE。
对于返回的整型来说,没有特别的含义,INCR表示递增数,LASTSAVE表示UNIX时间等。然而,返回的整型被保证在64位有符号整型的范围内。
整型响应被拓展,为了能返回true,false。例如,命令像EXIST或者SISMEMBER将要返回1为true、0为false。
其他的命令,例如SADD、SREM、SETNX将要返回1,如果操作被执行,0代表相反。以下的命令将会使用整型响应:
SETNX、DEL、EXISTS、INCR、INCRBY、DECR、DECRBY、DBSIZE、LASTSAVE、RENAMENX、MOVE、LLEN、SADD、SREM、SISMEMBER、SCARD
RESP BULK Strings
Bulk String 被用于表达一个二进制安全的字符串,长度最多可达512MB。
Bulk String 被以以下方式进行编码:
- 一个“$”字节后面跟一些字节,这些字节组成一个字符串(一个前缀长度),以CRLF结束。
- 实际的字符串
- 一个终结CRLF
所以“foobar”被编码如下:
“$6\r\nfoobar\r\n”
一个空字符串如下:
“$0\r\n\r\n”
RESP Bulk Strings 使用特殊格式能标识不存在的值(NULL)。这个特殊的格式是长度为-1,并且没有数据,所以NULL用如下标识:
“$-1\r\n”
这被称作Null Bulk String.
当服务器使用一个Null Bulk String回应时,客户端库API不应该翻译成一个空的字符串,而是应该返回一个nil object。
举个例子一个Ruby 库应该返回‘nil’,而一个c库应该返回NULL(或者设定一个特殊的符号)
RESP 数组(Arrays)
客户端使用RESP数组向服务器发送命令。类似的,Redis(返回元素集合的)命令使用RESP数组作为返回类型。一个例子是LRANGE命令--一个返回元素列表的命令
RESP数组使用下列格式发送:
- 一个“*”作为首字符,紧接着是数组元素的个数(一个数)。紧接着CRLF。
- 一个为标识数组中每个元素的类型而附加的RESP类型。
所以一个空数组,如下表示:
“*0\r\n”
当一个包含两个RESP Bulk String(“foo”、“bar”)的数组时被如下编码:
“*2\r\n$3\r\nfoo\r\nbar\r\n”
可以看出,经过*CRLF前缀,组成数组的其他的数据类型仅仅简单的拼接在一起。举个例子,一个有三个整型的数组被如下编码:
“*3\r\n:1\r\n:2\r\n:3\r\n”
数组也能包含混合类型,尽管这对于元素相同的数组时不需要的。举个例子,一个包含四个整型和一个Bulk string的列表能被如下编码:
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
响应,为了表达清晰被认为分成了多个行。
服务器第一行发送的是“*5\r\n”为了表达接下来将有五片数据,然后每一个响应组成的多个Multi Bulk被发送。
空数组的概念也是存在的,而且是有两种选择去指定空值(通常使用空Bulk String,但是由于历史原因,有两种方式)。
举个例子,当BLPOP命令超时时,返回一个Null Array,此数组包含一个-1作为数目,如下:
“*-1\r\n”
当Redis使用a NULL array回应时,一个客户端库API应该返回a null object而不是一个空数组。注意需要区分an empty list 和 一个不同的情况(例如BLPOP命令的超时)。
在RESP中,数组中的数组时有可能的。例如,包含两个数组的数组能被如下编码:
*2\r\n
*3\r\n
:1\r\n
:2\r\n
:3\r\n
*2\r\n
+Foo\r\n
-Bar\r\n
RESP 空元素(Null element in arrays)
单个数组元素可能是Null。这在Redis响应中表示元素丢失或者不是空字符串。这可能发生在,使用GET模式选项,而指定的key丢失情况下,执行SORT命令。
如下是一个回应包含Null元素:
*3\r\n
$3\r\n
foo\r\n
$-1\r\n
$3\r\n
bar\r\n
第二个元素是Null,客户端应该返回一些像:
[“foo”,nil,“bar”]
发送命令道Redis服务器
现在你对RESP串行化协议格式有所熟悉,写一个Redis客户端实现将会很容易。我们进一步阐述客户端和服务器端是如何交互的:
- 客户端发送仅包含Bulk String的RESP数组到服务器端。
- Redis服务器发送一些有效的RESP数据类型作为回应。
所以典型的交互可能如下
:
客户端发送LLEN mylist命令,为了去获取存储在服务器端中,以mylist作为key的列表长度,服务器使用整型回应客户端:
C: *2\r\n
C: $4\r\n
C: LLEN\r\n
C: $6\r\n
C: mylist\r\n
S: :48293\r\n
像前面一样,人为分割协议成多行,为了方便解释,但是实际交互客户端发 *2\r\n$4\r\nLLEN\r\n$8\r\nmylist\r\n
多命令和管道流
客户端为了发送多命令。可能使用相同的连接。Pipeline被支持,所以多命令可以在一次客户端写操作中被发送,不需要读取一条命令的执行情况,然后再发送下一个,所有的响应在最后能被读取。
内联命令(inline command)
有时候,你仅有telnet可用,你需要发送命令道Redis server。虽然Redis协议实现起来很简单,但是用它来使用交互session却是不理想的,redis-cli也许不总是可用的。所以,对于人来说,Redis以一种特殊的方式接受命令,这被叫做inline command格式。
这时可以使用内联命令:server/client使用内联命令通信的例子(服务器会话用“s”开头,客户端用“c”开头)
C:PING
S:+PONG
以下是另一个返回整型结果的内联命令的例子
C:EXIST somekey
S:0
基本上,在telnet session中,你简单写入空格分割参数。因为名两个不再以*开头,相反使用一个不适合的请求协议,Redis能够发现这种情况解析你的命令。
对于Redis协议的高性能解析器(inline command)
Redis协议阅读性良好,容易去实现,能被实现成类似二进制协议一样的高性能。
RESP使用前缀长度转换bulk数据,所以从不需要有,类似于JSON的额外扫描负担,也不会将此负担发送到服务器。
Bulk和Multi Bulk长度能被使用作用于单个字符串的代码进行处理,像如下代码:
#include
int main(void) {
unsigned char *p = "$123\r\n";
int len = 0;
p++;
while(*p != '\r') {
len = (len*10)+(*p - '0');
p++;
}
/* Now p points at '\r', and the len is in bulk_len. */
printf("%d\n", len);
return 0;
}
经过第一个CR被发现,它跳过下列的LF,不再扫描。当bulk数据能被读取使用一次扫描操作,不会引入其他负载,最后保持CRLF被丢弃而不经过任何处理。
当与二进制协议进行比较时,Redis协议更容易用高级语言实现,减少了在客户端软件中的bug。