原文: https://redis.io/topics/protocol
Reids客户端和redis服务器交流使用RESP协议(redis序列化协议),这个协议是设计是服务于redis的,在其他客户端-服务器软件工程中也可以使用这个协议。
RESP特点:简单实现,快速解析,人类可读
RESP可以序列化不同的数据类型如integers/strings/arrays。同时也定义了错误的专用类型。Redis客户端使用strings数组代表需要执行的命令,发送请求给服务端。Redis响应数据类型专用命令。
注意:协议只适用与客户端服务端交流,redis cluster使用不同的二进制协议交换节点之间的数据。
客户端连接redis服务器在6379端口创建了一个TCP 连接
虽然RESP在技术上是非TCP特定的 , 但在Redis的上下文中,协议仅用于TCP连接(或者面向流的连接如unix套接字)
Redis接受由不同参数组成的命令。 收到命令后,将对其进行处理,并将回复发送回客户端。
RESP从Redis 1.2开始使用,Redis 2.0开始成为Redis服务器标准的交互方式,需要在你的redis客户端中实现。
RESP是一个序列化协议支持如下数据类型:Simple Srings/Errors/Integers/Bulk Strings/Arrays。
RESP在redis中是一个请求应答协议
--客户端以Bulk Strings数组形式,发送命令给redis服务器
--服务器根据命令的实现回复一种RESP 类型
在RESP中,数据的类型依赖于第一个字节
--简单字符串(Simple Strings),第一个字节为”+”
--错误(Errors), 第一个字节为“-”
--整型(Integers), 第一个字节为“:”
--Bulk Strings, 第一个字节为“$”
--数组(Array),第一个字节为“*”
RESP 使用特殊的Bulk Strings变量来表示空值
RESP的不同部分始终使用”\r\n”(CRLF)结束
简单字符串按以下方式编码:加号字符,后跟不能包含CR或LF字符的字符串(不允许换行),使用CRLF终止(即“\ r \ n”)。
Simple Strings用于以最小的开销传输非二进制安全字符串。例如,许多Redis命令在成功时仅回复“OK”, RESP Simple String使用以下5个字节进行编码:
"+OK\r\n"
为了发送二进制安全字符串,使用RESP Bulk Strings。
当Redis使用Simple String回复时,客户端库应该向调用者返回一个字符串,该字符串由'+'之后的第一个字符到字符结尾组成,不包括CRLF字节。
RESP错误(Errors)
RESP具有特定的错误数据类型。实际上错误与RESP Simple Strings完全相同,但第一个字符是' - '字符而不是’+’。 RESP中简单字符串和错误之间的真正区别在于客户端将错误视为异常,组成错误类型的字符串是错误消息本身。基本格式是:
"-Error message\r\n"
错误回复仅在发生问题是时送,例如,尝试对错误的数据类型执行操作,或者命令不存在等等。收到错误答复时,库客户端应抛出异常。
以下是错误回复的示例:
-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value
“ - ”之后的第一个单词,直到第一个空格或换行符,表示返回的错误类型。这只是Redis使用的约定,不是RESP错误格式的一部分。
例如,ERR是一般错误,而WRONGTYPE时一个更具体的错误,意味着客户端尝试对错误的数据类型执行操作。这称为错误前缀,是一种允许客户端理解服务器返回的错误类型的方法,而不依赖于给定的确切消息,这可能随时间而变化。
客户端实现可以针对不同的错误返回不同类型的异常,或者直接将错误名称作为字符串提供给调用者,作为提供捕获错误的通用方法。
但是,这样的特性不应该被认为是至关重要的,因为它很少有用,并且有限的客户端实现可能只是返回一般的错误条件,例如false。
这个类型字符串,以“:”字节为前缀,CRLF作为终止符,如“:0\r\n”或“:1000\r\n”为整数回复。
许多Redis命令返回RESP整数,如: INCR,LLEN和LASTSAVE。
返回的整数没有特殊含义,它只是INCR的增量数,LASTSAVE的UNIX时间等等。但是,返回的整数在有符号的64位整数范围内。
整数回复也被广泛使用以返回真或假。例如,EXISTS或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。
Bulk Strings用于表示长度最大为512 MB的单个二进制安全字符串。
Bulk Strings按以下方式编码:
--一个“$”字节后跟组成字符串的字节数(一个前缀长度),由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对象。例如,Ruby库应返回'nil',而C库应返回NULL(或在reply对象中设置特殊标志),依此类推。
客户端使用RESP Arrays将命令发送到Redis服务器。类似地,某些Redis命令将元素集合返回给客户端,使用RESP Arrays作为回复类型。一个例子是LRANGE命令,它返回列表的元素。
RESP Arrays使用以下格式发送:
--一个*字符作为第一个字节,后跟数组中的元素数作为十进制数,后跟CRLF。
--Array的每个元素的附加RESP类型。
所以空数组如下:
"*0\r\n"
两个RESP Bulk Strings “foo”和“bar”的数组编码为:
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
数组前缀部分为*
"*3\r\n:1\r\n:2\r\n:3\r\n"
数组可以包含混合类型,元素不必是相同类型。例如,可以将包含四个整数和批量字符串的列表编码如下:
*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 reply。
Null Array的概念也存在,并且是指定Null值的替代方法(通常使用Null Bulk String,但由于历史原因,我们有两种格式)。
例如,当BLPOP命令超时时,它返回一个Null数组,其计数-1如下例所示:
"*-1\r\n"
当Redis使用Null数组回复时,客户端库API应返回空对象而不是空数组。这是区分空列表和不同条件(例如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数据类型编码一个由两个元素组成的数组,该数组包含三个整数1,2,3以及一个简单字符串和一个错误。
第二个元素是Null。客户端库应返回如下内容:
["foo",nil,"bar"]
请注意,这不是前面部分中所述的例外,而只是进一步指定协议的示例。
将命令发送到Redis服务器
现在您熟悉了RESP序列化格式,编写Redis客户端库的实现将很容易。我们可以进一步了解客户端和服务器之间的交互如何工作:
--客户端向Redis服务器发送一个仅由Bulk Strings组成的RESP Array。
--Redis服务器回复发送任何有效RESP数据类型作为回复的客户端。
因此,例如,典型的交互如下:
客户端发送命令LLEN mylist以获取存储在密钥mylist中的列表长度,服务器回复一个Integer消息,如下例所示(C:是客户端,S:服务器)。
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$6\r\nmylist\r\n作为一个整体发送。
客户端可以使用相同的连接来发出多个命令。支持流水线操作,因此客户端可以通过单个写入操作发送多个命令,而无需在发出下一个命令之前读取上一个命令的服务器回复。所有的回复都可以在最后阅读。
有时您只需要telnet并向Redis服务器发送命令。虽然Redis协议易于实现,但在交互式会话中使用并不理想,并且redis-cli可能并不总是可用。因此,Redis还以一种专为人类设计的特殊方式接受命令,并称为内联命令格式。
以下是使用内联命令进行服务器/客户端交互的示例(服务器聊天以S:开头,客户端与C聊天:)
C: PING
S: +PONG
以下是返回整数的内联命令的另一个示例:
C: EXISTS somekey
S: :0
基本上,您只需在telnet会话中编写以空格分隔的参数。由于没有命令以*开头,而是在统一请求协议中使用,因此Redis能够检测到这种情况并解析您的命令。
虽然Redis协议非常易读且易于实现,但它可以用类似于二进制协议的性能来实现。
RESP使用前缀长度来传输批量数据,因此永远不需要像json一样,扫描有效负载以查找特殊字符,也不需要引用需要发送到服务器的有效负载。
可以使用对每个字符执行单个操作的代码处理批量和多个批量长度,同时扫描CR字符,如下面的C代码:
#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一起跳过而不进行任何处理。然后,可以使用不以任何方式检查有效负载的单个读取操作来读取批量数据。最后,丢弃剩余的CR和LF字符而不进行任何处理。
虽然性能与二进制协议相当,但Redis协议在大多数非常高级语言中实现起来要简单得多,从而减少了客户端软件中的错误数量。
格式如下:
*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
例如:SET testkey testvalue
*3
$3
SET
$7
testkey
$9
testvalue
"*3\r\n$3\r\nSET\r\n$7\r\ntestkey\r\n$9\r\n testvalue\r\n"