Redis客户端使用名为RESP(Redis序列化协议)的协议与Redis服务器进行通信。 虽然该协议是专为Redis设计的,但它可以用于其他CS软件项目的通讯协议。
RESP是以下几方面的考虑:
RESP可以序列化不同的数据类型,如整型,字符串,数组。 还有一种特定的错误类型。 请求将要执行的命令作为字符串数组从Redis客户端发送到Redis服务器。Redis使用特定数据类型的命令进行回复。
RESP是二进制安全的,不需要处理从一个进程传输到另一个进程的批量数据,因为它使用前缀长度来传输批量数据。
注意:此处概述的协议仅用于客户端 - 服务器通信。 Redis Cluster使用不同的二进制协议,以便在节点之间交换消息。
客户端连接到Redis服务器,是创建TCP连接到端口6379。
虽然RESP在技术上是非TCP特定的,但在Redis的上下文中,协议仅用于TCP连接(或类似的面向流的连接,如Unix套接字)。
Redis接受由不同参数组成的命令。 收到命令后,将对其进行处理并将回复发送回客户端。
这是最简单的模型,但有两个例外:
排除上述两个例外,Redis协议是一个简单的请求 - 响应协议。
RESP协议在Redis 1.2中引入,但它成为与Redis 2.0中的Redis服务器通信的标准方式。 这是每一个Redis客户端中应该实现的协议。
RESP实际上是一个支持以下数据类型的序列化协议:单行字符串,错误信息,整型,多行字符串和数组。
RESP在Redis中用作请求 - 响应协议的方式如下:
在 RESP 中, 一些数据的类型通过它的第一个字节进行判断:
此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以“\ r \ n”(CRLF)结束。
简单字符串按以下方式编码:加号字符,后跟不能包含CR或LF字符的字符串(不允许换行),由CRLF终止(即“\ r \ n”)。
Simple Strings用于以最小的开销传输非二进制安全字符串。 例如,许多Redis命令成功回复时只有“OK”,因为RESP 单行字符串使用以下5个字节进行编码:
"+OK\r\n"
为了发送二进制安全字符串,使用RESP 多行字符串代替。
当Redis使用Simple String回复时,客户端库应该向调用者返回一个字符串,该字符串由“+”之后的第一个字符组成,直到字符串结尾,不包括最终的CRLF字节。
RESP具有错误的特定数据类型。 实际上错误与RESP 单行字符串完全相同,但第一个字符是减号’ - ‘字符而不是加号。
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。
多行字符串用于表示长度最大为512 MB的单个二进制安全字符串。
多行字符串按以下方式编码:
所以字符串“foobar”的编码如下:
"$6\r\nfoobar\r\n"
当只是一个空字符串时:
"$0\r\n\r\n"
RESP 多行字符串也可用于使用用于表示Null值的特殊格式来表示值的不存在。 在这种特殊格式中,长度为-1,并且没有数据,因此Null表示为:
"$-1\r\n"
当服务器使用Null 多行字符串回复时,客户端库API不应返回空字符串,而应返回nil对象。 例如,Ruby库应返回’nil’,而C库应返回NULL(或在reply对象中设置特殊标志),依此类推。
客户端使用RESP 数组将命令发送到Redis服务器。 类似地,某些Redis命令将元素集合返回给客户端使用RESP 数组是回复类型。 一个例子是LRANGE命令,它返回列表的元素。
RESP数组使用以下格式发送:
所以空数组就是以下内容:
"*0\r\n"
那么两个RESP批量字符串“foo”和“bar”的数组编码为:
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
正如您在数组前面加上* CRLF部分之后所看到的那样,组成数组的其他数据类型将一个接一个地连接起来。 例如,三个整数的数组编码如下:
"*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,以指定将跟随五个回复。 然后发送构成多重回复项目的每个回复。
Null 数组的概念也存在,并且是指定Null值的替代方法(通常使用Null 多行字符串,但由于历史原因,我们有两种格式)。
例如,当BLPOP命令超时时,它返回一个计数为-1的Null数组,如下例所示:
"*-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
第二个元素是Null。 客户端库应返回如下内容:
["foo",nil,"bar"]
注意,这不是前面部分中所述的例外,而只是进一步指定协议的示例。
既然熟悉RESP序列化格式,那么编写Redis客户端库的实现将很容易。 我们可以进一步讲述客户端和服务器之间的交互如何工作:
因此,例如,典型的交互可以是以下所示。
客户端发送命令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整体。
客户端可以使用同一个连接来发出多个命令。 支持流水线操作,因此客户端可以通过单个写入操作发送多个命令,而无需在发出下一个命令之前读取上一个命令的服务器回复,所有的回复都可以在最后阅读。
有关更多信息,请查看我们关于 Pipelining 的页面。
(译注: 对于基于像TCP这样的流式协议,Pipeling 实际上是一种协议的实现技术,站在服务端的角度就算它一次收到了多个命令,它也不知道客户端是一次发送了多个命令还是分了多次发送,但当服务器端一次收到多个命令时确实可以做一些优化处理,比如 优化 RTT, 多个命令的返回调用一次write系统调用从而减少系统调用的次数,提高吞吐量。)
有时您只能通过 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 <stdio.h>
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协议在大部分的高级语言实现起来足够简单,减少了客户端软件的bug数量。
(译注:
这是楼主第一次尝试翻译一篇技术文档,相对来说技术文档的英文阅读起来还是比较舒服的,相信有了第一次尝试,之后肯定会越来越顺利。由于楼主水平有限,文章中难免有纰漏,期望小伙伴的指出,感谢……。
作 者:haifeiWu
原文链接:https://cloud.tencent.com/developer/article/1333291
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。