RESP3协议概述
RESP3是RESP v2的更新版本,RESP v2是Redis中使用的协议,大约从版本2.0开始(1.2已经支持它,但是Redis 2.0是第一个只讨论这个协议的版本)。此协议的名称只是RESP3,而不是respv3或RESP3.0。
该协议用于处理客户机和服务器之间的请求-响应通信,其中客户机执行某种请求,服务器用一些数据进行回复。该协议特别适合于数据库,因为它能够返回复杂的数据类型和相关的信息来扩充返回的数据(例如给定信息的流行度指数)。
RESP3协议可以不对称地使用,就像在Redis中一样:客户端只能向服务器发送一个子集,而服务器可以返回可用的完整类型集。这是因为RESP设计用于发送非结构化命令,如 SET mykey somevalue
或 SADD myset a b c d
。此类命令可以表示为数组,其中每个参数都是数组元素,因此这是客户端需要发送到服务器的唯一类型。然而,愿意将RESP3用于其他目标的不同应用程序可能只允许以“全双工”方式使用协议,其中两端都可以使用可用的全套类型。
不是RESP3的所有部分对客户端和服务器都是必需的。在Redis的具体案例中,RESP3描述了某些在将来有用的功能,这些功能最初可能不会实现。RESP3的其他可选部分只能在特定情况下由Redis实现,比如主数据库与其副本之间的链接,或者特定状态下的客户端连接。
RESP3 类型
RESP3摒弃了RESP第二个版本中令人费解的措辞,使用了一个更易于理解的类型名称,因此在本文中您将看不到对批量回复或多批量回复的提及。
等同于RESP版本2的类型:
- 数组:N个其他类型的有序集合。
- Blob string:二进制安全字符串。
- 简单字符串:节省空间的非二进制安全字符串。
- 简单错误:一个节省空间的非二进制安全错误代码和消息。
- 数字:有符号64位范围内的整数
RESP3引入的类型:
- Null:替换RESP v2*-1和$-1 Null值的单个Null值。
- Double:浮点数。
- Boolean: true or false。
- Blob error:二进制安全错误代码和消息。
- Verbatim string:一个二进制安全字符串,应该在没有任何转义或过滤的情况下显示给人类。例如Redis
LATENCY DOCTOR
的输出。
- Map:键值对的有序集合。键和值可以是任何其他RESP3类型。
- Set:其他N种类型的无序集合。
- Attribute:与映射类型类似,但是客户机应该忽略属性类型继续读取回复,并将其作为附加信息返回给客户机。
- Push:带外数据。格式类似于数组类型,但是客户机应该只检查第一个string元素,说明带外数据的类型,如果有为这种特定类型的推送信息注册的回调,则调用回调。推送类型与回复无关,因为它们是服务器可以在连接中随时推送的信息,因此如果客户端正在读取命令的回复,则应继续读取。
- Hello:与映射类型类似,但仅在客户端和服务器之间建立连接时发送,以便用不同的信息(如服务器名称、版本等)欢迎客户端。
- Big number:不能用数字类型表示的大数
简单的类型
本节介绍所有不是聚合类型的RESP3类型。它们只包含一个类型化元素。
Blob string
一般形式是 $<length>\r\n<bytes>\r\n
。它基本上与RESP的早期版本完全相同。
字符串“hello world”由以下协议表示:
$11<CR><LF>
helloworld<CR><LF>
或作为转义字符串:
"$11\r\nhelloworld\r\n"
长度字段限制为无符号64位整数的范围。零是有效长度,因此空字符串由以下表达式表示:
"$0\r\n\r\n"
Simple string
一般形式是 +<string>\r\n
,因此“hello world”编码为
+hello world<CR><LF>
或作为转义字符串:
"+hello world\r\n"
简单字符串中不能包含或字符。
Simple error
这与简单的字符串非常相似,但初始字节是 -
而不是 +
:
-ERR this is the error description<CR><LF>
或作为转义字符串:
"-ERR this is the error description\r\n"
错误的第一个字是大写的,描述了错误代码。剩下的字符串是错误消息本身。错误代码是通用的。错误代码有助于客户机区分不同的错误条件,而不必在错误消息中进行模式匹配,这可能会改变。
Number
一般形式是 :<number>\r\n
,因此数字1234被编码为
:1234<CR><LF>
或作为转义字符串:
":1234\r\n"
有效数字在有符号64位整数的范围内。较大的数字应该改用大数字类型。
Null
空类型的编码方式为 _\r\n
,即下划线字符后跟 CR
和 LF
字符。
Double
一般形式是 ,<floating-point-number>\r\n
,例如1.23编码为:
,1.23<CR><LF>
或作为转义字符串:
",1.23\r\n"
首先。假设初始零无效 .
指数格式无效。要完全忽略小数部分,即后面跟有其他数字的点是有效的,因此可以使用数字或双精度格式返回数字10:
":10\r\n"
",10\r\n"
但是,如果实现客户的编程语言对这两种类型有明确的区分,那么应该在一种情况下返回浮点数,在另一种情况下返回整数。
此外,双重回复可能会返回正无穷大或负无穷大,如下两种情况:
",inf\r\n"
",-inf\r\n"
所以客户端实现应该能够正确地处理这个问题。
Boolean
真值和假值只是用 #t\r\n
和 #f\r\n
序列表示。在没有布尔类型的编程语言中实现的客户机库应该向客户机返回用于在此类语言中表示true和false的规范值。例如,C程序应该返回一个值为0或1的整数类型。
Blob error
一般形式是 !<length>\r\n<bytes>\r\n
。它与字符串类型完全相同。但是,与简单的错误类型一样,第一个大写字母表示错误代码。
错误“SYNTAX invalid SYNTAX”由以下协议表示:
!21<CR><LF>
SYNTAX invalid syntax<CR><LF>
或作为转义字符串:
"!21\r\nSYNTAX invalid syntax\r\n"
Verbatim string
这与Blob字符串类型完全相同,但初始字节是 =
而不是 $
。此外,前三个字节提供有关以下字符串格式的信息,可以是txt表示纯文本,也可以是mkd表示标记。第四个字节始终为:。接下来是真正的字符串。
例如,这是一个有效的verbatim string:
=15<CR><LF>
txt:Some string<CR><LF>
普通客户端库可能会完全忽略此类型和字符串类型之间的差异,并在这两种情况下返回字符串。然而,诸如命令行界面(例如redis-cli)之类的交互式客户机知道输出必须按原样呈现给人类用户,而不必引用字符串。
Big number
此类型表示超出数字类型可以表示的有符号64位数字范围的非常大的数字。一般形式为 (<big number>\r\n
,如以下示例所示:
(3492890328409238509324850943850943825024385<CR><LF>
或作为转义字符串:
"(3492890328409238509324850943850943825024385\r\n"
大数字可以是正数或负数,但不能包含小数部分。用支持大数字的语言编写的客户机库应该只返回一个大数字。当大的数字不可用时,客户机应该返回一个字符串,但是在可能的情况下表示应答是一个大整数(这取决于客户机库使用的API)。
聚合数据类型
到目前为止描述的类型都是简单类型,只定义给定类型的单个项。然而,RESP3的核心是能够从类型和协议的角度表示具有不同语义的不同类型的聚合数据类型。
一般来说,聚合类型有一个给定的格式,说明聚合的类型,以及聚合中有多少元素。接下来是单一元素。聚合类型的元素可以是其他聚合类型,因此可以有数组数组或集合映射,等等。通常Redis命令只会使用这些可能性的一个子集。但是,使用Lua脚本或使用Redis模块,任何组合都是可能的。然而,从客户机库的角度来看,这并不复杂:每个类型都完全指定客户机应该如何翻译它以向用户报告它,因此所有聚合的数据类型都实现为递归函数,然后读取N个其他类型。
RESP3中每个聚合类型的格式始终相同:
<aggregate-type-char><numelements><CR><LF> ... numelements other types ...
数组的聚合类型char是 *
,因此要表示一个包含三个数字1、2、3的数组,将发出以下协议:
*3<CR><LF>
:1<CR><LF>
:2<CR><LF>
:3<CR><LF>
或作为转义字符串:
"*3\r\n:1\r\n:2\r\n:3\r\n"
当然,数组也可以包含其他嵌套数组:
*2<CR><LF>
*3<CR><LF>
:1<CR><LF>
$5<CR><LF>
hello<CR><LF>
:2<CR><LF>
#f<CR><LF>
上面表示数组 [1,"hello",2],false]
客户端库应该返回一个合理类型的数组,该类型表示元素的有序序列,可以在常量或对数时间内随机索引访问。例如,Ruby客户机应该返回Ruby数组类型,而Python应该使用Python列表,以此类推。
Map type
Map精确地表示为数组,但编码值以 %
字节开始,而不是使用 *
字节。此外,下列元素的数目必须是偶数。映射表示字段值项的序列,基本上我们可以称之为字典数据结构,或者换句话说,是散列。
例如,用JSON表示的字典:
{
"first":1,
"second":2
}
在RESP3中表示为:
%2<CR><LF>
+first<CR><LF>
:1<CR><LF>
+second<CR><LF>
:2<CR><LF>
请注意,在 %
字符之后,跟数组中一样,后面不是单个项的数量,而是字段值对的数量。
Map可以有任何其他类型作为字段和值,但是Redis将只使用可用可能性的子集。例如,Redis命令不太可能返回一个数组作为键,但是Lua脚本和模块可能会这样做。
客户端库应该使用可用的惯用词典类型返回映射。然而,像C这样的低级语言可能仍然返回一个项目数组,但是带有类型信息,这样用户就可以知道回复实际上是一个字典。
Set reply
集合与数组类型完全相同,但第一个字节是 ~
而不是 *
:
~5<CR><LF>
+orange<CR><LF>
+apple<CR><LF>
#t<CR><LF>
:100<CR><LF>
:999<CR><LF>
但是,它们在语义上是不同的,因为所表示的项是无序的元素集合,所以客户机库应该返回一个类型,该类型虽然不一定是有序的,但有一个以常量或对数时间运行的存在性测试操作。
由于许多编程语言缺少本机集类型,一个明智的选择是返回一个散列,其中字段是集类型中的元素,值只是真值或任何其他值。
在较低级别的编程语言(如C)中,类型仍应报告为线性数组,并与类型信息一起通知用户它是一个集合类型。
通常设置的回复不应包含多次发出的相同元素,但协议不强制执行:客户端库应尝试处理此类情况,如果元素重复,则应尽力避免返回重复数据,至少在使用某种形式的哈希返回回复时是这样。否则,当返回一个仅读取协议所包含内容的数组时,客户端库可能会将重复项(如果存在)传递给调用者。许多实现会发现避免重复是很自然的。例如,他们将尝试在某个映射、散列或Set数据类型中添加每个read元素,再次添加相同的元素将替换旧的副本,或者将无声地失败,从而保留旧的副本。
Attribute type
属性类型与映射类型完全相同,但使用了 |
字节而不是 %
第一个字节。属性描述的字典与映射类型完全相同,但是客户机不应将此字典视为回复的一部分,而应仅考虑用于扩充回复的辅助数据。
例如,较新版本的Redis可能包括报告每个执行命令的键的流行程度的功能。因此,对命令 MGET a b
的回复可以如下所示:
|1<CR><LF>
+key-popularity<CR><LF>
%2<CR><LF>
$1<CR><LF>
a<CR><LF>
,0.1923<CR><LF>
$1<CR><LF>
b<CR><LF>
,0.0012<CR><LF>
*2<CR><LF>
:2039123<CR><LF>
:9543892<CR><LF>
对 MGET
的实际回复只是两项数组 [2039123,9543892]
,但是属性指定了原始命令中提到的键的流行程度(请求频率),作为从0到1的浮点数(至少在示例中,实际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<CR><LF>
:1<CR><LF>
:2<CR><LF>
|1<CR><LF>
+ttl
:3600
:3<CR><LF>
在上面的例子中,数组的第三个元素有一个关联的辅助信息{ttl:3600}. 请注意,不是由客户机库来解释属性,它们只是以合理的方式传递给调用者。
Push type
push连接是这样一种连接:协议的通常请求-响应模式不再为真,服务器可能会向客户端发送未明确请求的异步数据。
在Redis中,已经有了连接的概念,即在Redis协议的至少三个不同部分推送数据:
- Pub/Sub是一种推送模式连接,客户端在其中接收发布的数据。
未完待续。。