Redis序列化协议:RESP

RESP协议

读一下redis的序列化协议,顺便做点记录。

简介

RESP协议即 REdis Serialization Protocol(Redis序列化协议)。

RESP协议在Redis 1.2中引入,在Redis 2.0中成为了与Redis服务器通信的标准方式。这是所有Redis客户端都要遵循的协议,我们甚至可以基于此协议,开发实现自己的Redis客户端。该协议支持以下数据类型:简单字符串,错误类型,整数,批量字符串和数组。

官方文档:Redis serialization protocol specification | Redis

数据类型

简单字符串(Simple strings)

用于表示“非二进制安全”的字符串,开头一个加号( + ),后续字符串不能包含CR(\r)或者LF(\n),以CRLF(\r\n)结尾,举例:

"+OK\r\n"
简单错误类型(Simple errors)

错误类型和简单字符串类型相似,但以减号( - )开头。错误类型包含的字符串就是错误信息,基本格式如下:

"-Error message\r\n"

当服务器出错时才会向发送错误类型的数据,比如客户端要求服务器执行一个不存在的命令。下面是一些错误类型的数据:

-ERR unknown command 'helloworld'
-WRONGTYPE Operation against a key holding the wrong kind of value
整数(Integers)

整数类型的数据以冒号(:)开头,以CRLF(\r\n)结尾。举例:

":1000\r\n"
批量字符串(Bulk strings)

用于表示“二进制安全的字符串”。该类型数据包含两部分,首先是头部信息:美元符号($)开头,后紧跟字符串长度,以CRLF结尾。紧接着是数据部分,一个任意字符串,并同样以CRLF结尾。最大长度500M。举例:

"$5\r\nhello\r\n"
"$0\r\n\r\n"

批量字符串用长度-1表示空数据(Null Bulk String):

"$-1\r\n"
数组(Arrays)

数组以星号(*)开头,数组的序列化格式如下:

*<number-of-elements>\r\n<element-1>...<element-n>

第一个字节为星号(*),然后跟随一个无符号十进制数,例如:6、10、999等等。

空数组(长度为0的数组):

*0\r\n

两个bulk strings组成的数组:

*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n

三个整数组成的数组:

*3\r\n:1\r\n:2\r\n:3\r\n

数组可以包含混合数据类型,以下编码表示四个整数和一个批量字符串组成的数组(为了可读性,原始 RESP 编码被分成多行):

*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$5\r\n
hello\r\n

所有聚合 RESP 类型都支持嵌套。例如,两个数组的嵌套数组编码如下(为了可读性,原始 RESP 编码被分成多行):

*2\r\n
*3\r\n
:1\r\n
:2\r\n
:3\r\n
*2\r\n
+Hello\r\n
-World\r\n

空数组(数组类型的空对象):

*-1\r\n

当Redis回复空数组时,客户端应该返回空对象而不是空数组。这是区分空列表和不同条件(例如 BLPOP 命令的超时条件)所必需的。

数组中的空元素:

数组的单个元素可能是空的批量字符串。这在 Redis 回复中使用,以表明这些元素丢失且不是空字符串。例如,当 SORT 命令与选项一起使用时,如果指定的键丢失,则可能会发生这种情况。

以下是包含 null 元素的数组回复示例:

*3\r\n
$5\r\n
hello\r\n
$-1\r\n
$5\r\n
world\r\n

上面,第二个元素为空。客户端库应该向其调用者返回如下内容:

["hello",nil,"world"]
空值(Nulls)

null 数据类型表示不存在的值。

Nulls 的编码是下划线( _ )字符,后面是 CRLF 终止符()。Null 的原始 RESP 编码:

_\r\n
空批量字符串(Null Bulk String)、空数组(Null Arrays)和空值(Nulls)

由于历史原因,RESP2 采用两个特殊值来表示批量字符串和数组的空值。这种二元性一直是一种冗余,没有为协议本身增加任何语义价值,白白增加了复杂度。RESP3 中引入的 null 类型旨在修复此错误。

布尔值(Booleans)

RESP 布尔值编码如下:

#<t|f>\r\n

井号( # )开头,“t” 表示true,“f” 表示false,以CRLF结尾。

双精度浮点数(Doubles)

双精度数编码如下:

,[<+|->]<integral>[.<fractional>][<E|e>[sign]<exponent>]\r\n

逗号开头,CRLF结尾。去掉逗号和CRLF,中间这部分是就是用来表示浮点数的,类似科学计数法,但又不是严格意义的数学上的科学计数法。这种表示方法很常见,你可能在C语言中,或者Python中,又或者一些计算器中见过这种表示方法。

举例:

2^{100} -> 1.3e100

0.33^{-100} -> 0.33e-100

-9.3^{-5.6} -> -9.3e-5.6

你可能会问,{-9.3}^{-100} 怎么表示?不好意思,这是个算式,上面描述的表示方法是用来表示数字的。你需要将负号提到括号外面去,保证它不需要计算就能一眼看出正负。至于这个算式,它到底是正是负,需要计算之后才知道,这种就不行。

回到RESP序列化协议。

数字 1.23 的编码:

,1.23\r\n

由于小数部分是可选的(optional),因此整数值十 (10) 可以被 RESP 编码为整数和双精度:

:10\r\n
,10\r\n

正无穷大、负无穷大和 NaN 值编码如下:

,inf\r\n
,-inf\r\n
,nan\r\n
大数字(Big numbers)

大数字使用以下编码:

([+|-]<number>\r\n

左括号( ( )字符作为第一个字节。

举例:

(3492890328409238509324850943850943825024385\r\n
批量错误(Bulk errors)

这种类型将简单错误的目的与批量字符串的表达能力结合起来。

编码:

!<length>\r\n<error>\r\n

感叹号( !)作为第一个字节,length表示错误消息的长度,以字节为单位。

例如,错误 “SYNTAX invalid syntax” 由以下协议编码表示(为了可读性,原始 RESP 编码被分成多行):

!21\r\n
SYNTAX invalid syntax\r\n
逐字字符串(Verbatim strings)

此类型类似于批量字符串,此外还提供有关数据编码的提示。

逐字字符串的 RESP 编码如下:

=<length>\r\n<encoding>:<data>\r\n

等号( = )作为第一个字节。

举例(为了可读性,原始 RESP 编码被分成多行):

=15\r\n
txt:Some string\r\n
哈希表(Maps)

编码方式如下:

%<number-of-entries>\r\n<key-1><value-1>...<key-n><value-n>

百分号( % )开头,后紧跟键值对数量。键和值都可以是任何 RESP 类型。

例如,以下 JSON 对象:

{
    "first": 1,
    "second": 2
}

可以像这样在 RESP 中编码(为了可读性,原始 RESP 编码被分成多行):

%2\r\n
+first\r\n
:1\r\n
+second\r\n
:2\r\n
集合(Sets)

集合有点像数组,但是是无序的,并且应该只包含唯一的元素。

编码方式如下:

~<number-of-elements>\r\n<element-1>...<element-n>

波形符 ( ~ ) 作为第一个字节,后面紧跟元素数量。

推送(Pushes)

推送事件的编码方式与数组类似,仅第一个字节不同:

><number-of-elements>\r\n<element-1>...<element-n>
客户端握手(Client handshake)

新的 RESP 连接应通过调用 HELLO 命令来开始会话。这种做法完成了两件事:

  1. 它允许服务器向后兼容 RESP2 版本。 Redis 需要这样做,以便更温和地过渡到协议版本 3。
  2. HELLO 命令返回有关服务器和客户端可用于不同目标的协议的信息。

HELLO 命令具有以下高级语法:

HELLO <protocol-version> [optional-arguments]

命令的第一个参数是我们想要设置连接的协议版本。默认情况下,连接以 RESP2 模式启动。如果我们指定的连接版本太大并且服务器不支持,它应该回复 -NOPROTO 错误。例子:

Client: HELLO 4
Server: -NOPROTO sorry, this protocol version is not supported.

此时,客户端可以使用较低的协议版本重试。

同样,客户端可以轻松检测到只能使用 RESP2 的服务器:

Client: HELLO 3
Server: -ERR unknown command 'HELLO'

请注意,即使支持该协议的版本, HELLO 命令也可能返回错误、不执行任何操作并保持在 RESP2 模式。例如,当在命令的可选 AUTH 子句中使用无效的身份验证凭据时:

Client: HELLO 3 AUTH default mypassword
Server: -ERR invalid password
(the connection remains in RESP2 mode)

HELLO 命令的成功回复是 Map 回复。回复中的信息部分取决于服务器,但某些字段对于所有 RESP3 实现都是必需的:

  • server:服务器软件名
  • version:服务器的版本。
  • proto:RESP 协议支持的最高版本。

在 Redis 的 RESP3 实现中,还会返回以下字段:

  • id:连接的标识符(ID)
  • mode:"standalone", "sentinel" or "cluster"
  • role:"master" or "replica"
  • modules:已加载模块列表,一个类型为Bulk Strings的数组。

向 Redis 服务器发送命令(Sending commands to a Redis server)

典型的交互可能如下所示:

客户端发送命令 LLEN mylist 来获取存储在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 作为一个整体发送。

多命令和流水线(Multiple commands and pipelining)

客户端可以使用同一个连接发出多个命令。支持流水线,因此客户端可以通过单个写入操作发送多个命令。客户端可以跳过读取回复并继续一个接一个地发送命令。所有回复都可以在文末阅读。

有关详细信息,请参阅 管道化

内联命令(Inline commands)

有时您可能需要向 Redis 服务器发送命令,但只有 telnet 可用。虽然 Redis 协议实现起来很简单,但它对于交互式会话来说并不理想,并且 redis-cli 可能并不总是可用。因此,Redis 也接受内联命令格式的命令。

以下示例演示了使用内联命令进行服务器/客户端交换(服务器聊天以 S: 开头,客户端聊天以 C: 开头):

C: PING
S: +PONG

这是内联命令的另一个示例,其中服务器返回一个整数:

C: EXISTS somekey
S: :0
posted @ 2023-08-24 00:02  烟酒忆长安  阅读(589)  评论(0编辑  收藏  举报