Redis协议

Redis协议规范
Redis客户端使用称为RESP(REdis序列化协议)的协议与Redis服务器进行通信。虽然该协议是专为Redis设计的,但它可以用于其他客户端-服务器软件项目。

RESP是以下方面的折衷方案:

易于实现。
快速解析。
人类可读。
RESP可以序列化不同的数据类型,例如整数,字符串,数组。还有一种特定的错误类型。请求以字符串数组的形式从客户端发送到Redis服务器,这些字符串表示要执行的命令的参数。Redis使用特定于命令的数据类型进行回复。

RESP是二进制安全的,并且不需要处理从一个进程传输到另一个进程的批量数据,因为它使用前缀长度来传输批量数据。

注意:此处概述的协议仅用于客户端-服务器通信。Redis Cluster使用不同的二进制协议以在节点之间交换消息。

网络层
客户端连接到Redis服务器,以创建与端口6379的TCP连接。

尽管RESP在技术上不是特定于TCP的,但在Redis的上下文中,该协议仅与TCP连接(或等效的面向流的连接,如Unix套接字)一起使用。

请求-响应模型
Redis接受由不同参数组成的命令。收到命令后,将对其进行处理并将答复发送回客户端。

这是最简单的模型,但是有两个例外:

Redis支持流水线化(在本文档后面介绍)。因此,客户端可以一次发送多个命令,并在以后等待答复。
当Redis客户端订阅Pub / Sub通道时,该协议会更改语义并成为推送协议,也就是说,客户端不再需要发送命令,因为服务器会自动向客户端发送新消息(对于客户端的通道订阅)。
除了上述两个例外,Redis协议是一种简单的请求-响应协议。

RESP协议说明
RESP协议是在Redis 1.2中引入的,但是它成为Redis 2.0中与Redis服务器通信的标准方法。这是您应该在Redis客户端中实现的协议。

RESP实际上是支持以下数据类型的序列化协议:简单字符串,错误,整数,大容量字符串和数组。

RESP在Redis中用作请求-响应协议的方式如下:

客户端将命令作为大容量字符串的RESP数组发送到Redis服务器。
服务器根据命令实现以RESP类型之一进行回复。
在RESP中,某些数据的类型取决于第一个字节:

对于简单字符串,答复的第一个字节为“ +”
对于错误,回复的第一个字节为“-”
对于整数,答复的第一个字节为“:”
对于批量字符串,答复的第一个字节为“ $”
对于数组,回复的第一个字节为“ *”
另外,RESP可以使用Bulk Strings或Array的特殊变体来表示Null值,如稍后指定。

在RESP中,协议的不同部分始终以“\r\n”(CRLF)终止。

RESP简单字符串
简单字符串的编码方式如下:一个加号,后跟一个不能包含CR或LF字符的字符串(不允许换行),以CRLF终止(即“ \r\n”)。

简单字符串用于以最小的开销传输非二进制安全字符串。例如,许多Redis命令在成功后仅回答“ OK”,这是作为RESP简单字符串编码的以下5个字节:

"+OK\r\n"
为了发送二进制安全的字符串,请使用RESP批量字符串。

当Redis用简单字符串答复时,客户端库应向调用者返回一个字符串,该字符串由'+'之后的直到字符串末尾的第一个字符组成,不包括最后的CRLF字节。

RESP错误
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。

RESP整数
此类型只是一个CRLF终止的字符串,它表示一个整数,以“:”字节为前缀。例如,“:0 \ r \ n”或“:1000 \ r \ n”是整数回复。

许多Redis命令返回RESP整数,像INCR,LLEN和LASTSAVE。

返回的整数没有特殊含义,它只是INCR的增量数,LASTSAVE的UNIX时间等等。但是,保证返回的整数在带符号的64位整数范围内。

为了返回true或false,还广泛使用Integer答复。例如,像EXISTS或SISMEMBER这样的命令将为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。

RESP散装弦
使用大容量字符串来表示单个最大长度为512 MB的二进制安全字符串。

批量字符串以以下方式编码:

“ $”字节,后跟组成字符串的字节数(带前缀的长度),由CRLF终止。
实际的字符串数据。
最后的CRLF。
因此,字符串“ foobar”的编码如下:

"$6\r\nfoobar\r\n"
当一个空字符串只是:

"$0\r\n\r\n"
还可以使用RESP散装字符串,以使用用于表示Null值的特殊格式来表示值的不存在。在这种特殊格式中,长度为-1,并且没有数据,因此Null表示为:

"$-1\r\n"
这称为Null Bulk String。

当服务器以Null Bulk String答复时,客户端库API不应返回空字符串,而应返回nil对象。例如,Ruby库应返回“ nil”,而C库应返回NULL(或在reply对象中设置特殊标志),依此类推。

RESP阵列
客户端使用RESP阵列将命令发送到Redis服务器。类似地,某些Redis命令将元素集合返回给客户端使用RESP数组是回复类型。一个示例是返回列表元素的LRANGE命令。

RESP数组使用以下格式发送:

一个*字符作为第一个字节,然后是数组中的元素数(作为十进制数),然后是CRLF。
数组中每个元素的附加RESP类型。
因此,空数组只是以下内容:

"*0\r\n"
两个RESP散列字符串“ foo”和“ bar”的数组编码为:

"*2\r\n$3\\r\\nfoo\\r\\n$3\r\nbar\r\n"
如您所见,*<count>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 Bulk String,但出于历史原因,我们有两种格式)。

例如,当BLPOP命令超时时,它将返回一个Null Array,其计数为,-1如以下示例所示:

"*-1\r\n"
当Redis回复Null Array时,客户端库API应该返回一个空对象而不是一个空Array。区分空白列表和其他条件(例如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数据类型对两个元素Array进行编码,该数组由一个包含三个Integer 1、2、3的数组以及一个Simple String和Error的数组组成。

数组中的空元素
数组的单个元素可以为Null。在Redis答复中使用此命令是为了表示这些元素丢失并且不是空字符串。当缺少指定键时,当与GET 模式选项一起使用时,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客户端库的实现将变得很容易。我们可以进一步指定客户端和服务器之间的交互方式:

客户端向Redis服务器发送仅由批量字符串组成的RESP数组。
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协议的高性能解析器
虽然Redis协议非常易于阅读,并且易于实现,但是可以实现与二进制协议相似的性能。

RESP使用带前缀的长度来传输批量数据,因此,无需像在JSON中那样扫描有效负载中的特殊字符,也无需引用需要发送到服务器的有效负载。

可以使用对每个字符执行单个操作的代码来处理Bulk和Multi Bulk长度,同时扫描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协议在性能上可与二进制协议相提并论,但在大多数高级语言中实现起来却要简单得多,从而减少了客户端软件中的错误数量。

https://redis.io/topics/protocol

添加新评论