Redis协议规范(译文)

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

RESP是以下几方面的考虑:

  • 易于实现
  • 快速解析
  • 可读性高

RESP可以序列化不同的数据类型,如整型,字符串,数组。 还有一种特定的错误类型。 请求将要执行的命令作为字符串数组从Redis客户端发送到Redis服务器。Redis使用特定数据类型的命令进行回复。

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

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

网络层

客户端连接到Redis服务器,是创建TCP连接到端口6379。
虽然RESP在技术上是非TCP特定的,但在Redis的上下文中,协议仅用于TCP连接(或类似的面向流的连接,如Unix套接字)。

请求 - 响应模型

Redis接受由不同参数组成的命令。 收到命令后,将对其进行处理并将回复发送回客户端。
这是最简单的模型,但有两个例外:

  • Redis支持流水线操作(本文档稍后介绍)。 因此,客户端可以一次发送多个命令,并等待稍后的回复。
  • 当Redis客户端处于 Pub/Sub 时,协议会更改语义并成为推送协议,即客户端不再需要发送命令,因为服务器会在它们接收到命令时发自动向客户端发送新消息。

排除上述两个例外,Redis协议是一个简单的请求 - 响应协议。

RESP 协议描述

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

RESP实际上是一个支持以下数据类型的序列化协议:单行字符串,错误信息,整型,多行字符串和数组。
RESP在Redis中用作请求 - 响应协议的方式如下:

  • 客户端将命令作为字符串数组发送到Redis服务器。
  • 服务器根据命令实现回复一种RESP类型数据。

在 RESP 中, 一些数据的类型通过它的第一个字节进行判断:

  • 单行回复:回复的第一个字节是 "+"

  • 错误信息:回复的第一个字节是 "-"

  • 整形数字:回复的第一个字节是 ":"

  • 多行字符串:回复的第一个字节是 "$"

  • 数组:回复的第一个字节是 "*"

此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以“\ r \ n”(CRLF)结束。

RESP 单行字符串(简单字符串)

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

Simple Strings用于以最小的开销传输非二进制安全字符串。 例如,许多Redis命令成功回复时只有“OK”,因为RESP 单行字符串使用以下5个字节进行编码:

"+OK\r\n"

为了发送二进制安全字符串,使用RESP 多行字符串代替。

当Redis使用Simple String回复时,客户端库应该向调用者返回一个字符串,该字符串由“+”之后的第一个字符组成,直到字符串结尾,不包括最终的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位整数范围内。

整数回复也被广泛使用,以便返回真或假。 例如,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。

RESP 多行字符串

多行字符串用于表示长度最大为512 MB的单个二进制安全字符串。

多行字符串按以下方式编码:

  • 一个“$”字节后跟组成字符串的字节数(一个前缀长度),由CRLF终止。
  • 字符串数据。
  • 最终的CRLF。

所以字符串“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 数组

客户端使用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"

正如您在数组前面加上* 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"]

注意,这不是前面部分中所述的例外,而只是进一步指定协议的示例。

发送命令到 Redis 服务端

既然熟悉RESP序列化格式,那么编写Redis客户端库的实现将很容易。 我们可以进一步讲述客户端和服务器之间的交互如何工作:

  • 客户端向Redis服务器发送仅由Bulk Strings组成的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整体。

小结

这是楼主第一次尝试翻译一篇技术文档,相对来说技术文档的英文阅读起来还是比较舒服的,相信有了第一次尝试,之后肯定会越来越顺利。由于楼主水平有限,文章中难免有纰漏,期望小伙伴的指出,感谢……。

参考链接

原文地址: haifeiWu的博客
博客地址:www.hchstudio.cn
欢迎转载,转载请注明作者及出处,谢谢!

posted @ 2018-08-08 09:47  haifeiWu  阅读(876)  评论(0编辑  收藏  举报