Redis 序列化协议规范

Redis 序列化协议 (RESP) 是客户端实现的线缆协议

为了与 Redis 服务器通信,Redis 客户端使用一种称为 Redis 序列化协议 (RESP) 的协议。虽然该协议是专为 Redis 设计的,但您也可以将其用于其他客户端-服务器软件项目。

RESP 是以下考虑因素之间的权衡

  • 易于实现。
  • 解析速度快。
  • 人类可读。

RESP 可以序列化不同的数据类型,包括整数、字符串和数组。它还具有错误特定类型。客户端将请求作为字符串数组发送给 Redis 服务器。数组的内容是服务器应执行的命令及其参数。服务器的回复类型是特定于命令的。

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

RESP 是您在 Redis 客户端中应该实现的协议。

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

RESP 版本

对 RESP 协议第一个版本的支持是在 Redis 1.2 中引入的。在 Redis 1.2 中使用 RESP 是可选的,主要目的是消除协议中的一些问题。

在 Redis 2.0 中,协议的下一个版本,即 RESP2,成为客户端与 Redis 服务器的标准通信方法。

RESP3 是 RESP2 的超集,主要目的是让客户端作者的生活更轻松一些。Redis 6.0 引入了对 RESP3 功能的实验性可选支持(不包括流式字符串和流式聚合)。此外,HELLO 命令的引入允许客户端握手并升级连接的协议版本(参见客户端握手)。

直到并包括 Redis 7,RESP2 和 RESP3 客户端都可以调用所有核心命令。但是,命令可能对不同的协议版本返回不同类型的回复。

Redis 的未来版本可能会更改默认协议版本,但 RESP2 不太可能完全废弃。然而,未来版本中的新功能可能需要使用 RESP3。

网络层

客户端通过创建到其端口的 TCP 连接来连接到 Redis 服务器(默认端口是 6379)。

虽然 RESP 在技术上不特定于 TCP,但在 Redis 的上下文中,该协议专门用于 TCP 连接(或等效的面向流的连接,如 Unix 套接字)。

请求-响应模型

Redis 服务器接受由不同参数组成的命令。然后,服务器处理命令并将回复发送回客户端。

这是可能的最简单的模型;但是,也有一些例外

  • Redis 请求可以被流水线化。流水线化使客户端能够一次发送多个命令,稍后再等待回复。
  • 当 RESP2 连接订阅 Pub/Sub 通道时,协议会改变语义并成为一个推送协议。客户端不再需要发送命令,因为服务器会在收到新消息时自动将新消息发送给客户端(对于客户端订阅的通道)。
  • MONITOR 命令。调用MONITOR 命令会将连接切换到临时推送模式。此模式的协议未指定,但易于解析。
  • 保护模式。当 Redis 处于保护模式时,来自非回环地址的连接将被服务器拒绝和终止。在终止连接之前,无论客户端是否向套接字写入数据,Redis 都会无条件地发送一个 -DENIED 回复。
  • RESP3 推送类型。顾名思义,推送类型允许服务器向连接发送带外数据。服务器可以随时推送数据,且数据不一定与客户端执行的特定命令相关。

除了这些例外,Redis 协议是一个简单的请求-响应协议。

RESP 协议说明

RESP 本质上是一个序列化协议,支持几种数据类型。在 RESP 中,数据的第一个字节决定了其类型。

Redis 通常以以下方式将 RESP 用作请求-响应协议

  • 客户端将命令作为数组发送给 Redis 服务器,数组只包含批量字符串。数组中的第一个(有时也是第二个)批量字符串是命令的名称。数组的后续元素是命令的参数。
  • 服务器以 RESP 类型回复。回复的类型由命令的实现以及可能的客户端协议版本决定。

RESP 是一种二进制协议,使用标准 ASCII 编码的控制序列。例如,字符 A 编码为二进制字节值 65。类似地,字符 CR (\r)、LF (\n) 和 SP ( ) 的二进制字节值分别为 13、10 和 32。

\r\n (CRLF) 是协议的终结符,它始终分隔协议的各个部分。

RESP 序列化负载中的第一个字节始终标识其类型。后续字节构成该类型的内容。

我们将每个 RESP 数据类型归类为简单类型批量类型聚合类型

简单类型类似于编程语言中的标量,表示纯字面值。布尔值和整数就是此类示例。

RESP 字符串分为简单类型批量类型。简单字符串永远不包含回车符 (\r) 或换行符 (\n)。批量字符串可以包含任何二进制数据,也可以称为二进制blob。请注意,批量字符串可以由客户端进一步编码和解码,例如使用宽多字节编码。

聚合类型,如数组和映射,可以具有不同数量的子元素和嵌套级别。

下表总结了 Redis 支持的 RESP 数据类型

RESP 数据类型 最低协议版本 类别 第一个字节
简单字符串 RESP2 简单 +
简单错误 RESP2 简单 -
整数 RESP2 简单 :
批量字符串 RESP2 聚合 $
数组 RESP2 聚合 *
空值 RESP3 简单 _
布尔值 RESP3 简单 #
双精度浮点数 RESP3 简单 ,
大数 RESP3 简单 (
批量错误 RESP3 聚合 !
逐字字符串 RESP3 聚合 =
映射 RESP3 聚合 %
属性 RESP3 聚合 `
集合 RESP3 聚合 ~
推送 RESP3 聚合 >

简单字符串

简单字符串编码为加号(+),后跟一个字符串。该字符串不得包含 CR (\r) 或 LF (\n) 字符,并以 CRLF(即 \r\n)终止。

简单字符串以最小的开销传输短的非二进制字符串。例如,许多 Redis 命令在成功时仅回复“OK”。此简单字符串的编码是以下 5 个字节

+OK\r\n

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

要发送二进制字符串,请改用批量字符串

简单错误

RESP 具有特定的错误数据类型。简单错误(或简称错误)类似于简单字符串,但其第一个字符是减号(-)。简单字符串和 RESP 中的错误之间的区别在于,客户端应将错误视为异常,而错误类型中编码的字符串是错误消息本身。

基本格式是

-Error message\r\n

Redis 仅在出现问题时回复错误,例如,当您尝试对错误的数据类型进行操作时,或者当命令不存在时。客户端在收到错误回复时应引发异常。

以下是错误回复的示例

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

- 后面的第一个大写单词,直到第一个空格或换行符,表示返回的错误类型。此单词称为错误前缀。请注意,错误前缀是 Redis 使用的约定,而不是 RESP 错误类型的一部分。

例如,在 Redis 中,ERR 是通用错误,而 WRONGTYPE 是更具体的错误,表示客户端尝试对错误的数据类型进行操作。错误前缀允许客户端理解服务器返回的错误类型,而无需检查确切的错误消息。

客户端实现可以针对各种错误返回不同类型的异常,或者通过直接向调用者提供错误名称作为字符串来提供捕获错误的通用方法。

但是,不应将此功能视为至关重要,因为它很少有用。此外,更简单的客户端实现可以返回一个通用错误值,例如 false

整数

此类型是一个以 CRLF 终止的字符串,表示一个带符号的 64 位十进制整数。

RESP 以以下方式编码整数

:[<+|->]<value>\r\n
  • 冒号(:)作为第一个字节。
  • 可选的加号(+)或减号(-)作为符号。
  • 一个或多个十进制数字(0..9)作为整数的无符号十进制值。
  • CRLF 终结符。

例如,:0\r\n:1000\r\n 是整数回复(分别为零和一千)。

许多 Redis 命令返回 RESP 整数,包括 INCRLLENLASTSAVE。整数本身除了返回它的命令的上下文之外没有特殊意义。例如,对于 INCR,它是一个递增的数字;对于 LASTSAVE,它是一个 UNIX 时间戳;等等。但是,返回的整数保证在带符号的 64 位整数范围内。

在某些情况下,整数可以表示布尔值的真和假。例如,SISMEMBER 对于真返回 1,对于假返回 0。

其他命令,包括 SADDSREMSETNX,当数据发生变化时返回 1,否则返回 0。

批量字符串

批量字符串表示单个二进制字符串。字符串可以是任意大小,但默认情况下,Redis 将其限制为 512 MB(请参阅 proto-max-bulk-len 配置指令)。

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

$<length>\r\n<data>\r\n
  • 美元符号($)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为字符串的长度(以字节为单位),是一个无符号的十进制值。
  • CRLF 终结符。
  • 数据。
  • 最后的 CRLF。

因此,字符串 "hello" 的编码如下

$5\r\nhello\r\n

空字符串的编码是

$0\r\n\r\n

空批量字符串

虽然 RESP3 有专门的空值数据类型,但 RESP2 没有此类类型。相反,由于历史原因,RESP2 中空值的表示是通过批量字符串数组类型的预定形式。

空批量字符串表示不存在的值。GET 命令在目标键不存在时返回空批量字符串。

它被编码为长度为负一 (-1) 的批量字符串,如下所示

$-1\r\n

当服务器以空批量字符串回复时,Redis 客户端应返回一个 nil 对象,而不是空字符串。例如,Ruby 库应返回 nil,而 C 库应返回 NULL(或在回复对象中设置一个特殊标志)。

数组

客户端将命令作为 RESP 数组发送到 Redis 服务器。类似地,一些返回元素集合的 Redis 命令使用数组作为其回复。例如,LRANGE 命令返回列表的元素。

RESP 数组的编码使用以下格式

*<number-of-elements>\r\n<element-1>...<element-n>
  • 星号(*)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为数组中的元素数量,是一个无符号的十进制值。
  • CRLF 终结符。
  • 数组中每个元素的附加 RESP 类型。

所以空数组仅是以下内容

*0\r\n

而包含两个批量字符串 "hello" 和 "world" 的数组的编码是

*2\r\n$5\r\nhello\r\n$5\r\nworld\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
$5\r\n
hello\r\n

(为方便阅读,原始 RESP 编码已拆分成多行)。

服务器发送的第一行是 *5\r\n。这个数字值告诉客户端接下来将有五种回复类型。然后,每个连续的回复构成数组中的一个元素。

所有聚合 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

(为方便阅读,原始 RESP 编码已拆分成多行)。

上面编码了一个包含两个元素的数组。第一个元素是另一个数组,依次包含三个整数 (1, 2, 3)。第二个元素是另一个数组,包含一个简单字符串和一个错误。

Multi bulk reply(多批量回复)
在某些地方,RESP 数组类型可能被称为multi bulk。两者是相同的。

空数组

虽然 RESP3 有专门的空值数据类型,但 RESP2 没有此类类型。相反,由于历史原因,RESP2 中空值的表示是通过批量字符串数组类型的预定形式。

空数组作为表示空值的另一种方式存在。例如,当 BLPOP 命令超时时,它返回一个空数组。

空数组的编码是长度为 -1 的数组,即

*-1\r\n

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

数组中的空元素

数组的单个元素可能是空批量字符串。这在 Redis 回复中用于表示这些元素是缺失而不是空字符串。例如,在使用带 GET pattern 选项的 SORT 命令时,如果指定的键缺失,就可能发生这种情况。

以下是一个包含空元素的数组回复示例

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

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

["hello",nil,"world"]

空值

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

空值的编码是下划线(_)字符,后跟 CRLF 终结符(\r\n)。以下是空值的原始 RESP 编码

_\r\n
空批量字符串、空数组和空值

由于历史原因,RESP2 具有两个专门制作的值,用于表示批量字符串和数组的空值。这种双重性一直是冗余的,它没有为协议本身增加任何语义值。

RESP3 中引入的空类型旨在纠正这一错误。

布尔值

RESP 布尔值编码如下

#<t|f>\r\n
  • 井号(#)作为第一个字节。
  • 对于真值,使用字符 t;对于假值,使用字符 f
  • CRLF 终结符。

双精度浮点数

双精度浮点数 RESP 类型编码一个双精度浮点数值。双精度浮点数编码如下

,[<+|->]<integral>[.<fractional>][<E|e>[sign]<exponent>]\r\n
  • 逗号(,)作为第一个字节。
  • 可选的加号(+)或减号(-)作为符号。
  • 一个或多个十进制数字(0..9)作为无符号十进制整数值。
  • 一个可选的点(.),后跟一个或多个十进制数字(0..9)作为无符号十进制小数部分值。
  • 一个可选的大写或小写字母 E(Ee),后跟可选的加号(+)或减号(-)作为指数的符号,以一个或多个十进制数字(0..9)作为无符号十进制指数值结束。
  • CRLF 终结符。

以下是数字 1.23 的编码

,1.23\r\n

因为小数部分是可选的,所以整数值 10(十)既可以编码为整数,也可以编码为双精度浮点数

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

在这种情况下,Redis 客户端应分别返回本地整数和双精度浮点数值,前提是其实现语言支持这些类型。

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

,inf\r\n
,-inf\r\n
,nan\r\n

大数

此类型可以编码超出带符号 64 位整数范围的整数值。

大数使用以下编码

([+|-]<number>\r\n
  • 左括号(()作为第一个字节。
  • 可选的加号(+)或减号(-)作为符号。
  • 一个或多个十进制数字(0..9)作为无符号十进制值。
  • CRLF 终结符。

示例

(3492890328409238509324850943850943825024385\r\n

大数可以是正数或负数,但不能包含小数部分。用支持大数类型的语言编写的客户端库应返回大数。如果不支持大数,客户端应返回字符串,并在可能的情况下向调用者表明回复是一个大整数(取决于客户端库使用的 API)。

批量错误

此类型结合了简单错误的目的和批量字符串的表达能力。

它的编码如下

!<length>\r\n<error>\r\n
  • 感叹号(!)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为错误的长度(以字节为单位),是一个无符号的十进制值。
  • CRLF 终结符。
  • 错误本身。
  • 最后的 CRLF。

按照惯例,错误以一个大写字母(用空格分隔)开头,该单词传达错误消息。

例如,错误 "SYNTAX invalid syntax" 的协议编码如下

!21\r\n
SYNTAX invalid syntax\r\n

(为方便阅读,原始 RESP 编码已拆分成多行)。

逐字字符串

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

逐字字符串的 RESP 编码如下

=<length>\r\n<encoding>:<data>\r\n
  • 等号(=)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为字符串的总长度(以字节为单位),是一个无符号的十进制值。
  • CRLF 终结符。
  • 恰好三个 (3) 字节表示数据的编码。
  • 冒号(:)字符分隔编码和数据。
  • 数据。
  • 最后的 CRLF。

示例

=15\r\n
txt:Some string\r\n

(为方便阅读,原始 RESP 编码已拆分成多行)。

某些客户端库可能会忽略此类型和字符串类型之间的区别,并在两种情况下都返回本地字符串。但是,交互式客户端,例如命令行界面(如redis-cli),可以使用此类型并知道其输出应按原样呈现给人类用户,而无需引用字符串。

例如,Redis 命令 INFO 输出包含换行符的报告。当使用 RESP3 时,redis-cli 会正确显示它,因为它作为逐字字符串回复发送(其三个字节为 "txt")。然而,当使用 RESP2 时,redis-cli 硬编码查找 INFO 命令以确保其正确显示给用户。

映射

RESP 映射编码键值对的集合,即字典或哈希表。

它的编码如下

%<number-of-entries>\r\n<key-1><value-1>...<key-n><value-n>
  • 百分号(%)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为映射中的条目数(或键值对数),是一个无符号的十进制值。
  • CRLF 终结符。
  • 映射中每个键和值的附加 RESP 类型。

例如,以下 JSON 对象

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

可以像这样在 RESP 中编码

%2\r\n
+first\r\n
:1\r\n
+second\r\n
:2\r\n

(为方便阅读,原始 RESP 编码已拆分成多行)。

映射的键和值都可以是任何 RESP 类型。

Redis 客户端应返回其语言提供的惯用字典类型。但是,低级编程语言(例如 C)可能会返回一个数组,同时提供类型信息以向调用者指示它是一个字典。

RESP2 中的映射模式
RESP2 没有映射类型。RESP2 中的映射由包含键和值的扁平数组表示。第一个元素是键,后跟相应的值,然后是下一个键,依此类推,如下所示:key1, value1, key2, value2, ...

属性

属性类型与映射类型完全相同,但第一个字节不是 % 字符,而是使用 | 字符。属性描述字典的方式与映射类型完全相同。但是,客户端不应将此类字典视为回复的一部分,而应将其视为增强回复的辅助数据。

注意:在下面的示例中,缩进仅为清晰起见;额外的空白不属于真实回复的一部分。

例如,更新版本的 Redis 可能包含报告每个执行命令的键的流行度的功能。命令 MGET a b 的回复可能如下所示

|1\r\n
    +key-popularity\r\n
    %2\r\n
        $1\r\n
        a\r\n
        ,0.1923\r\n
        $1\r\n
        b\r\n
        ,0.0012\r\n
*2\r\n
    :2039123\r\n
    :9543892\r\n

MGET 的实际回复只是包含两个项的数组 [2039123, 9543892]。返回的属性指定了原始命令中提及的键的流行度或请求频率,以 0.01.0 之间的浮点数给出。注意:Redis 中的实际实现可能有所不同。

当客户端读取回复并遇到属性类型时,它应该读取属性,并继续读取回复。属性回复应该单独累积,并且用户应该有一种方法来访问此类属性。例如,如果我们想象一个更高级语言的会话,可能会发生以下情况

> r = Redis.new
#<Redis client>
> r.mget("a","b")
#<Redis reply>
> r
[2039123,9543892]
> r.attribs
{:key-popularity => {:a => 0.1923, :b => 0.0012}}

属性可以出现在协议标识给定类型的任何有效部分之前,并且仅提供紧随其后的回复部分的信息。例如

*3\r\n
    :1\r\n
    :2\r\n
    |1\r\n
        +ttl\r\n
        :3600\r\n
    :3\r\n

在上面的示例中,数组的第三个元素关联了 {ttl:3600} 的辅助信息。请注意,这不属于客户端库的任务来解释属性,但它应该以合理的方式将它们传递给调用者。

集合

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

RESP 集合的编码是

~<number-of-elements>\r\n<element-1>...<element-n>
  • 波浪号(~)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为集合中的元素数量,是一个无符号的十进制值。
  • CRLF 终结符。
  • 集合中每个元素的附加 RESP 类型。

如果其编程语言中提供了本地集合类型,客户端应返回该类型。或者,在没有本地集合类型的情况下,可以使用数组以及类型信息(例如在 C 中)。

推送

RESP 的推送包含带外数据。它们是协议请求-响应模型的一个例外,并为连接提供了通用的推送模式

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

><number-of-elements>\r\n<element-1>...<element-n>
  • 大于号(>)作为第一个字节。
  • 一个或多个十进制数字(0..9)作为消息中的元素数量,是一个无符号的十进制值。
  • CRLF 终结符。
  • 推送事件中每个元素的附加 RESP 类型。

推送的数据可以出现在任何 RESP 数据类型之前或之后,但永远不会在其内部。这意味着客户端不会在例如映射回复中间找到推送数据。这也意味着推送数据可以出现在命令回复之前或之后,也可以单独出现(不调用任何命令)。

客户端应通过调用实现其推送数据处理的 callback 来响应推送。

客户端握手

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

  1. 它允许服务器向后兼容 RESP2 版本。这在 Redis 中是必要的,以使过渡到协议的第三版本更平缓。
  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'

然后客户端可以继续使用 RESP2 与服务器通信。

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

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

HELLO 命令的成功回复是映射回复。回复中的信息部分取决于服务器,但某些字段对于所有 RESP3 实现都是强制性的

  • server:“redis”(或其他软件名称)。
  • version:服务器的版本。
  • proto:RESP 协议支持的最高版本。

在 Redis 的 RESP3 实现中,还会发出以下字段

  • id:连接的标识符(ID)。
  • mode:“standalone”、“sentinel”或“cluster”。
  • role:“master”或“replica”。
  • modules:已加载模块的列表,以批量字符串数组形式表示。

向 Redis 服务器发送命令

既然您已经熟悉了 RESP 序列化格式,您就可以使用它来帮助编写 Redis 客户端库。我们可以进一步指定客户端和服务器之间的交互方式

  • 客户端向 Redis 服务器发送一个只包含批量字符串的数组
  • Redis 服务器向客户端回复,发送任何有效的 RESP 数据类型作为回复。

因此,例如,典型的交互可能如下所示。

客户端发送命令 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

多个命令和流水线

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

有关更多信息,请参阅流水线

内联命令

有时您可能需要向 Redis 服务器发送命令,但只能使用 telnet。虽然 Redis 协议易于实现,但它并不适合交互式会话,而且 redis-cli 可能并不总是可用。因此,Redis 也接受内联命令格式的命令。

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

C: PING
S: +PONG

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

C: EXISTS somekey
S: :0

基本上,要发出内联命令,您可以在 telnet 会话中写入用空格分隔的参数。由于没有命令以 *(RESP 数组的标识字节)开头,Redis 会检测到这种情况并内联解析您的命令。

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 length is in len. */
    printf("%d\n", len);
    return 0;
}

识别出第一个 CR 后,可以跳过它以及后面的 LF,而无需进一步处理。然后,可以使用一次读取操作读取批量数据,该操作不会以任何方式检查有效负载。最后,剩余的 CR 和 LF 字符会被丢弃,无需额外处理。

虽然性能与二进制协议相当,但 Redis 协议在大多数高级语言中实现起来要简单得多,从而减少了客户端软件中的错误数量。

Redis 客户端作者的提示

  • 出于测试目的,请使用 Lua 的类型转换 让 Redis 以所需的任何 RESP2/RESP3 格式回复。例如,可以像这样生成一个 RESP3 double
    EVAL "return { double = tonumber(ARGV[1]) }" 0 1e0
    
评价此页面
返回顶部 ↑