Redis Protocol specification(Redis协议规范)

Redis客户端和服务端通信使用的协议称为RESP(Redis序列化协议)。


Redis 协议

Redis协议规范

Redis客户端和服务端通信使用的协议成为RESP(Redis序列化协议)。该协议是专门为Redis设计的,当然也可以用于其他 client-server 的软件项目。

RESP 是一种基于以下几点考虑上的折中协议

  • 简单实现
  • 快速解析
  • 人类可读

RESP 能够将不同的数据类型序列化,比如 整型、字符串、数组,也有一个特定的错误类型。请求从客户端发送到服务器的Redis作为代表该命令的参数来执行字符串数组。Redis使用特定数据类型来响应客户端请求。

RESP 是一种二进制安全的,并且不需要在进程间批量传输的协议,因为它使用前缀长度去批量数据传输。

这里概述的协议只用户客户端-服务端通信。Redis Cluster 使用的是一种旨在节点间消息交换的不同的二进制协议。


网络层

Redis Client通过 建立一个TCP 连接到端口6379 来连接Redis Server

然而 RESP 在技术上并非特定的TCP,在Redis 协议仅仅被用在与TCP建立连接(或者等效的面向流的连接,如 Unix Sockets)的上下文中。


请求响应模型

Redis 接收由不同参数组成的命令。一旦命令被接收到,它就会被服务端处理并且会发送响应给客户端。

这可能是最简单的模型,然而有另外两种情况:

  • Redis 支持管道流。因此 它使客户端一次发送多个命令成为可能,之后等待答复/响应。
  • 当一个 Redis 客户端订阅一个Pub/Sub channel 时,协议就会转换语义变成一种push协议,此时客户端不再需要发送命令,因为服务器会自动给客户端发送消息,这些消息会被尽可能快的收到。

不包括以上两点,Redis 就是一个简单的 请求-响应 协议。


协议描述

Redis 1.2 时就介绍过 RESP,但是从 Redis 2.0 开始,他成为一种与 Redis Server 通信的标准协议。这个协议就是在 Redis 客户端要实现的协议。

RESP 实际上是一个支持以下数据类型序列化的协议:简单字符串、整型、字符串、数组、错误。

RESP 作为一种请求-响应协议以以下方式被用在 Redis 中:

  • RESP 作为一个大字符串数组 被Client 发送命令到 Redis Server
  • Server 端 用根据命令实现的其中的一种 RESP 类型来响应Client 请求。

RESP 协议中,数据类型取决于第一个字节:

  • 对于简单字符串,响应的第一个字节是 “+
  • 对于错误,响应的第一个字节是 “-
  • 对于整型,响应的第一个字节是 “:
  • 对于批量字符串,响应的第一个字节是 “$
  • 对于数组,响应的第一个字节是 “*

此外,RESP 可以使用一个特定的批量字符串或数组的特殊变化来表示一个空值。
RESP 协议的不同部分都终止了与"\r\n"CRLF)。


RESP Simple Strings

简单字符串的编码方式如下:+字符之后紧跟着一个不包含CRLF字符的字符串(不允许换行),终止符是 CRLF("\r\n").

简单字符串是用来传输非二进制安全字符串的最小开销。例如 Redis 恢复OK时,只需要用以下5个字节进行编码 "+OK\r\n"

为了发送二进制安全的字符串,使用 RESP 批量字符串来代替。当 Redis 回复/响应 一个简单字符串时,客户端应该返回到调用一个由第一个字符后的"+"为结尾的字符串的字符串,不包含终止 CRLF字节。

RESP Errors

RESP Errors 是一个特殊的数据类型。其实错误类型 跟 RESP 简单字符串很像,只不过它的第一个字符是一个减号"-" ,而不是 “+”。简单字符串 和 ErrorsRESP 中真正的区别是 Errors 在客户端被作为异常处理,并且构成 Errors 类型的字符串就是错误信息本身。
基本格式如下:

1
"-Error Message\r\n"

错误响应只会在发生错误时发送,例如如果你试图执行一个对错误数据类型的操作,或者执行一个不存在的命令,等等。当收到错误响应时,应该由客户端引发一个异常。
以下就是错误响应的示例:

1
2
-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value

第一个词后的"-",到第一个空格或换行,代表返回的错误类型。这个仅仅是 Redis 的一个约定并不是 RESP Errors 错误格式的一部分。

例如,ERR是一般错误,而WRONGTYPE是一个更加具体的错苏,意味着客户端试图执行一个针对错误数据类型的操作。这被称为一个错误前缀,是一种允许客户端理解服务端返回的错误类型的方式,它不依赖于给定的确切消息。
客户端实现可能会为不同的错误返回不同的异常类型,或者可能提供一个通用的方法来捕获异常,该方法直接把错误的名称作为字符串提供类调用方。
然而,这样的功能不应该被认为是最关重要的,因为它很少被用到,而且客户端实现可能只是返回一个通用的错误条件,例如 false。


RESP Integers

这种类型仅仅只用 CRLF 终止符代表一个整数,以”:” 作为前缀。如:

“:0\r\n”
“:1000\r\n”

这些都是整数响应。
很多 Redis 命令返回 RESP整数,向 INCR、LLEN 、LASTSAVE。
返回的整数没有特殊的意义,只是一个INCR的数字递增或者是执行LASTSAVE的Unix 时间等等。但是,返回的整数必须保证是在一个有符号的64位整数范围内。整型响应被广泛使用以返回 TURE 或 FALSE。比如像 EXISTS 、 SISMEMBER命令返回1表示 TURE,0 表示 FALSE。
其他命令比如像 SADD、SETNX,如果操作执行成功则返回1,否则返回0。
以下命令将返回整数响应:

SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE,RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD。


RESP Bulk Strings

字符串块 或者 批量字符串
我习惯上成为 批量字符串,此处显然 翻译成 字符串块 更合适

字符串块被用来表示一个二进制安全的最大长度为 512MB 的字符串。
块字符串以下列方式编码:

  • “$”字节依次组成的字符串的字节数(前缀长度),以 CRLF终止。
  • 真实的字符串数据
  • 最后一个是回车换行符(CRLF : \r\n)

所以字符串”foobar”的编码格式如下:

“$6\r\nfoobar\r\n”

空字符串

“$0\r\n\r\n”

RESP 字符串块也可以用于不存在的值,需要使用特殊的格式来表示一个 NULL值。在这个特殊的格式中,长度为 -1,并且没有数据,所以NULL 表示为:

“$-1\r\n”

这被称为一个空字符串。
当服务器用一个空字符串响应时,客户端API不应该返回一个空字符串,而应该是一个nil对象。例如,Ruby API应该返回’nil’,而C API应该返回NULL(或者设置一个特殊的标识符),等等。


RESP Arrays

客户端使用RESP Arrays发送命令到Redis Server。同样,某些 Redis 命令 使用 RESP Arrays 作为响应类型 返回给客户端集合元素。像 LRANGE 命令 就需要返回含有多个值的list列表。
使用以下格式来发送 RESP Arrays

一个 * 字符作为第一个字节,随后紧跟数组元素的个数,之后是 CRLF \r\n
RESP 数组类型的每个元素

因此,可以使用如下方式表示一个空数组:

“*0\r\n”

包含字符串块 “foo”和”bar”的数组的编码方式如下:

“*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n”

正如你所看到的,*<count>\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 是为了指明有5个响应。 然后由这些响应(应答)组成一个多批量的响应Item被传输。
Redis 也定义了空数组,它也是指定空值的另一种方式(通常使用空批量字符串Null Bulk String ,但是由于历史原因,它有两种格式来表示)。
例如,当 BLPOP 超时 时,返回一个有-1计数的空数组,如下:

“*-1\r\n”

当 Redis 响应一个空数组时,Redis客户端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。在Redis响应这个使用这种是为了发信号通知这个元素已经丢失且不是空字符串。当使用SORT命令的GET模式选项且缺少指定key时会发横这种情况。空数组响应示例如下

*3\r\n
$3\r\n
foo\r\n
$-1\r\n
$3\r\n
bar\r\n

第二个元素是Null。而客户端应该像这样返回:

[“foo”,nil,”bar”]

需要注意的是,这不是之前章节中所述的一个例外,这里只是使用这个例子来进一步说明特定协议。


发送命令到 Redis Server

现在如果你已经熟悉了RESP序列化协议,那么你将会很容易的编写一个Redis客户端库的实现。我们可以进一步详细说明一下客户端和服务端的交互是如何工作的:

  • 客户端发送一个由多个字符串组成的RESP数组到服务端
  • Redis 服务端发送任何有效的RESP数据类型作为答复来响应给客户端

因此,可以以以下示例讲解这个简单的交互:
为了获取存储key’的mylist的长度,的客户端 发送LLEN mylist命令,然后服务端用一个整数来响应(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也接收一种为人类设计的特殊方式的命令,这种命令被称为内联命令格式。

下面是使用内联命令完成 server/client聊天的示例(S:服务器聊天,C:客户端聊天):

C: PING
S: +PONG

下面是一个内联命令返回一个整数的示例:

C: EXISTS somekey
S: :0

基本上你现在可以在一个telnet会话中简单的写一个空格分隔参数。由于没有请求是以以*(*被使用在统一请求协议中)开头的,因此Redis能够检测到这种情况并分析出你的命令。


高性能Redis协议解析器

Redis协议是人类可读而且容易实现的,因此它可以使用一种性能相似的二进制协议来实现。
RESP使用前缀来传输批量数据的产长度,因此永远不需要扫描特殊字符(例如JSON)的负载,也不需要引用需要发送到服务器的有效负载。

批量和多个批量的长度可以用执行单一个字符的单个操作的代码来处理,虽然同时会扫描到CR字符,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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一样不需要任何处理的跳过。然后,可以使用一个不以任何方式检查有效负载的单度操作来读取大量数据。最后,其余的CRLF字符会被丢弃,没有任何处理。

而性能相媲美的二进制协议的Redis协议简单实现了多数高层次的语言,减少客户端软件的bug数。


翻译自:Reids-Protocol
下一篇:Redis批量导入
另 : Redis Windows版本下载