Redis 序列化协议规范

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

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

RESP 是以下考虑因素之间的折衷方案:

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

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

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

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

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

RESP 版本

Redis 1.2 引入了对第一版 RESP 协议的支持。在 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 请求可以pipelined。 Pipelining 允许客户端一次性发送多个命令,并在稍后等待回复。
  • 当RESP2连接订阅Pub/Sub频道时,协议会改变语义并成为推送协议。 客户端不再需要发送命令,因为服务器一旦接收到新消息(针对客户端订阅的频道),就会自动将其发送给客户端。
  • MONITOR 命令。 调用 MONITOR 命令将连接切换到临时推送模式。 此模式的协议未指定,但解析起来很明显。
  • Protected mode. 从非回环地址连接到处于保护模式下的Redis时,连接会被服务器拒绝并终止。 在终止连接之前,Redis会无条件发送一个-DENIED回复,无论客户端是否向套接字写入数据。
  • RESP3 Push 类型。 顾名思义,推送类型允许服务器向连接发送带外数据。 服务器可以随时推送数据,且这些数据不一定与客户端执行的特定命令相关。

除了这些例外,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 数据类型 最低协议版本 类别 第一个字节
Simple strings RESP2 简单 +
Simple Errors RESP2 简单 -
Integers RESP2 简单 :
Bulk strings RESP2 聚合 $
Arrays RESP2 聚合 *
Nulls RESP3 简单 _
Booleans RESP3 简单 #
Doubles RESP3 简单 ,
Big numbers RESP3 简单 (
Bulk errors RESP3 聚合 !
Verbatim strings RESP3 聚合 =
Maps RESP3 聚合 %
Attributes RESP3 聚合 `
Sets RESP3 聚合 ~
Pushes RESP3 聚合 >

简单字符串

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

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

+OK\r\n

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

要发送二进制字符串,请使用bulk strings代替。

简单错误

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结尾的字符串,表示一个有符号的、基数为10的64位整数。

RESP 以以下方式编码整数:

:[<+|->]<value>\r\n
  • 冒号 (:) 作为第一个字节。
  • 可选的加号(+)或减号(-)作为符号。
  • 一个或多个十进制数字(0..9)作为整数的无符号、基数为10的值。
  • 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)作为字符串的长度,以字节为单位,作为无符号的基-10值。
  • 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)作为数组中元素的数量,表示为无符号的基数为10的值。
  • CRLF 终止符。
  • 数组中每个元素的额外RESP类型。

所以一个空数组就是如下所示:

*0\r\n

而由两个大字符串 "hello" 和 "world" 组成的数组的编码为:

*2\r\n$5\r\nhello\r\n$5\r\nworld\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
$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)。 第二个元素是另一个数组,包含一个简单的字符串和一个错误。

多批量回复:
在某些地方,RESP数组类型可能被称为多批量。 这两者是相同的。

空数组

尽管RESP3有专门的数据类型用于空值,但RESP2没有这样的类型。相反,由于历史原因,RESP2中空值的表示是通过批量字符串数组类型的预定形式来实现的。

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

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

*-1\r\n

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

数组中的空元素

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

这是一个包含空元素的数组回复示例:

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

上面,第二个元素是null。 客户端库应该返回给调用者类似这样的内容:

["hello",nil,"world"]

空值

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

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

_\r\n
Null Bulk String, Null Arrays and Nulls:

由于历史原因,RESP2 设计了两种特殊的值来表示批量字符串和数组的空值。 这种双重性一直是一种冗余,对协议本身没有任何语义上的价值。

在RESP3中引入的null类型旨在修复这个错误。

布尔值

RESP 布尔值编码如下:

#<t|f>\r\n
  • 第一个字节为井号字符(#)。
  • 一个t字符表示真值,或一个f字符表示假值。
  • CRLF 终止符。

双精度浮点数

Double RESP 类型编码了一个双精度浮点值。 双精度浮点数的编码方式如下:

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

这是数字1.23的编码:

,1.23\r\n

因为小数部分是可选的,所以十(10)的整数值可以同时以整数和双精度浮点数的形式进行RESP编码:

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

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

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

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

大数据

这种类型可以编码超出有符号64位整数范围的整数值。

大数字使用以下编码:

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

示例:

(3492890328409238509324850943850943825024385\r\n

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

批量错误

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

它被编码为:

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

按照惯例,错误信息以一个大写(空格分隔)的单词开头,用于传达错误信息。

例如,错误“SYNTAX 无效语法”由以下协议编码表示:

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

(原始的RESP编码为了可读性被分割成多行)。

逐字字符串

这种类型类似于bulk string,额外提供了关于数据编码的提示。

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

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

示例:

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

(原始的RESP编码为了可读性被分割成多行)。

一些客户端库可能会忽略这种类型和字符串类型之间的区别,并在两种情况下都返回原生字符串。 然而,交互式客户端,如命令行界面(例如,redis-cli),可以使用这种类型,并知道它们的输出应该原样呈现给人类用户,而不需要引用字符串。

例如,Redis命令INFO输出一个包含换行符的报告。 当使用RESP3时,redis-cli正确显示它,因为它作为Verbatim String回复发送(其三个字节为"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, ...

属性

属性类型与Map类型完全相同,但不是以%字符作为第一个字节,而是使用|字符。属性描述了一个字典,与Map类型完全相同。然而,客户端不应将这样的字典视为回复的一部分,而应将其视为增强回复的辅助数据。

注意:在下面的示例中,缩进仅用于清晰显示;额外的空白不会出现在实际回复中。

例如,较新版本的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)作为集合中元素的数量,作为无符号的基-10值。
  • CRLF 终止符。
  • 集合中每个元素的额外RESP类型。

如果客户端在其编程语言中有可用的原生集合类型,则应返回原生集合类型。 或者,在没有原生集合类型的情况下,可以使用带有类型信息的数组(例如,在C语言中)。

推送

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

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

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

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

客户端应通过调用实现推送数据处理逻辑的回调函数来响应推送。

客户端握手

新的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'

客户端可以继续使用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作为一个整体发送。

多个命令和管道

客户端可以使用相同的连接来发出多个命令。 支持管道操作,因此客户端可以通过一次写入操作发送多个命令。 客户端可以跳过读取回复,并继续一个接一个地发送命令。 所有的回复可以在最后读取。

欲了解更多信息,请参阅Pipelining

内联命令

有时你可能需要向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双精度数:
    EVAL "return { double = tonumber(ARGV[1]) }" 0 1e0
    
RATE THIS PAGE
Back to top ↑