作者:朱雷 2026-10-23
本文依据《RESP3 规范》整理,覆盖 RESP3 的设计动机、数据类型、请求/响应帧格式、连接协商、异步推送、流式编码、解析器设计以及可操作的 Redis 实验。文中的
<CR><LF>表示实际的两个字节0x0D 0x0A,而不是字面文本。适读人群:Redis 使用者、客户端/驱动开发者、网络协议学习者、需要排查 Redis 连接与响应解析问题的工程师。
目录
- 前言
- [RESP3 是什么:先建立整体认识](#RESP3 是什么:先建立整体认识)
- [为什么需要 RESP3:RESP2 的边界与改进方向](#为什么需要 RESP3:RESP2 的边界与改进方向)
- [RESP3 的线协议基础:字符、长度与 CRLF](#RESP3 的线协议基础:字符、长度与 CRLF)
- [11 类数据类型详解](#11 类数据类型详解)
- [5.1 Blob String(二进制安全字符串)](#5.1 Blob String(二进制安全字符串))
- [5.2 Simple String 与 Simple Error](#5.2 Simple String 与 Simple Error)
- [5.3 Number、Double、Boolean 与 Null](#5.3 Number、Double、Boolean 与 Null)
- [5.4 Blob Error](#5.4 Blob Error)
- [5.5 Verbatim String 与 Big Number](#5.5 Verbatim String 与 Big Number)
- 聚合类型与递归解析
- 属性、推送与"请求-响应"模型的变化
- [7.1 Attribute:不改变主回复的附加信息](#7.1 Attribute:不改变主回复的附加信息)
- [7.2 Push:可与普通回复交错的异步数据](#7.2 Push:可与普通回复交错的异步数据)
- 流式字符串与流式聚合
- [8.1 流式字符串](#8.1 流式字符串)
- [8.2 流式聚合](#8.2 流式聚合)
- [HELLO 握手、认证与兼容降级](#HELLO 握手、认证与兼容降级)
- [使用 redis-cli 进行实战观察](#使用 redis-cli 进行实战观察)
- [10.1 查看协议协商结果](#10.1 查看协议协商结果)
- [10.2 用 MONITOR 或抓包观察命令](#10.2 用 MONITOR 或抓包观察命令)
- [10.3 观察不同 Redis 数据结构对应的 RESP3 语义](#10.3 观察不同 Redis 数据结构对应的 RESP3 语义)
- [10.4 Pub/Sub 与 Push](#10.4 Pub/Sub 与 Push)
- [编写 RESP3 客户端:编码、解析与异步事件](#编写 RESP3 客户端:编码、解析与异步事件)
- [11.1 请求编码原则](#11.1 请求编码原则)
- [11.2 解析器的最小状态机](#11.2 解析器的最小状态机)
- [11.3 同步客户端与异步客户端](#11.3 同步客户端与异步客户端)
- 工程实践中的坑点、测试与安全
- [12.1 最容易踩的协议坑](#12.1 最容易踩的协议坑)
- [12.2 推荐测试集](#12.2 推荐测试集)
- [12.3 安全与资源控制](#12.3 安全与资源控制)
- 总结:如何把规范落到系统设计
1. 前言
本文依据《RESP3 规范》整理,覆盖 RESP3 的设计动机、数据类型、请求/响应帧格式、连接协商、异步推送、流式编码、解析器设计以及可操作的 Redis 实验。文中的 <CR><LF> 表示实际的两个字节 0x0D 0x0A,而不是字面文本。
适读人群:Redis 使用者、客户端/驱动开发者、网络协议学习者、需要排查 Redis 连接与响应解析问题的工程师。
| 项目 | 说明 |
|---|---|
| 协议名称 | RESP3(正式名称不是 RESP v3 或 RESP 3.0) |
| 承载方式 | 通常运行在 TCP 连接之上,使用 CRLF 作为行/帧分隔符 |
| 设计重点 | 可读、简单、可流式解析,并让响应携带更丰富的语义类型 |
| 兼容策略 | 连接初始按 RESP2 解释;通过 HELLO 3 协商 RESP3 |
| 原始依据 | 用户提供的《respv3 规范.docx》,规范版本演进至 1.6 |
2. RESP3 是什么:先建立整体认识
RESP(Redis Serialization Protocol)是 Redis 客户端与服务器之间交换命令和结果的协议。客户端把一条命令编码后写入连接,服务器解析命令并写回结果。RESP3 是 RESP 的第三版:它保留了 RESP2 简单、可读、便于流式解析的骨架,同时补充了布尔值、浮点数、集合、映射、属性、推送、二进制错误和流式数据等能力。
理解 RESP3 最重要的视角不是"记住几个前缀",而是把它看作一棵由类型标记构成的递归数据树。每个值先用一个字节声明类型,再用 CRLF 结束头部;聚合类型随后包含若干个完整的 RESP 值。
客户端发送(命令仍可表示为一个数组):
1. 前言
本文依据《RESP3 规范》整理,覆盖 RESP3 的设计动机、数据类型、请求/响应帧格式、连接协商、异步推送、流式编码、解析器设计以及可操作的 Redis 实验。文中的 <CR><LF> 表示实际的两个字节 0x0D 0x0A,而不是字面文本。
适读人群:Redis 使用者、客户端/驱动开发者、网络协议学习者、需要排查 Redis 连接与响应解析问题的工程师。
| 项目 | 说明 |
|---|---|
| 协议名称 | RESP3(正式名称不是 RESP v3 或 RESP 3.0) |
| 承载方式 | 通常运行在 TCP 连接之上,使用 CRLF 作为行/帧分隔符 |
| 设计重点 | 可读、简单、可流式解析,并让响应携带更丰富的语义类型 |
| 兼容策略 | 连接初始按 RESP2 解释;通过 HELLO 3 协商 RESP3 |
| 原始依据 | 用户提供的《respv3 规范.docx》,规范版本演进至 1.6 |
2. RESP3 是什么:先建立整体认识
RESP(Redis Serialization Protocol)是 Redis 客户端与服务器之间交换命令和结果的协议。客户端把一条命令编码后写入连接,服务器解析命令并写回结果。RESP3 是 RESP 的第三版:它保留了 RESP2 简单、可读、便于流式解析的骨架,同时补充了布尔值、浮点数、集合、映射、属性、推送、二进制错误和流式数据等能力。
理解 RESP3 最重要的视角不是"记住几个前缀",而是把它看作一棵由类型标记构成的递归数据树。每个值先用一个字节声明类型,再用 CRLF 结束头部;聚合类型随后包含若干个完整的 RESP 值。
客户端发送(命令仍可表示为一个数组):
*2<CR><LF>
$3<CR><LF>
GET<CR><LF>
$3<CR><LF>
key<CR><LF>
服务器返回:
$5<CR><LF>
value<CR><LF>
上例中,*2 表示数组有两个元素;每个元素是 Blob String(二进制安全字符串)。Redis 命令参数天然是有序列表,因此客户端到服务器的方向主要使用数组和字符串即可;服务器到客户端则可以使用 RESP3 的全部类型。
3. 为什么需要 RESP3:RESP2 的边界与改进方向
RESP2 的设计非常成功:文本可读、协议短小、客户端实现成本低。但随着 Redis 数据结构和客户端生态发展,几个问题逐渐显现:
- 语义不足 :
LRANGE、SMEMBERS、HGETALL在 RESP2 中都可能表现为数组,客户端必须记住命令才能决定转换成列表、集合还是映射。 - 类型不足:浮点数通常作为字符串、布尔值通常作为整数返回;客户端无法仅凭线上数据判断真实语义。
- 空值表示重复 :
null bulk与null multi-bulk两套表示没有带来足够的语义收益。 - 错误不够二进制安全:Simple Error 不能包含 CR 或 LF,通用协议 API 需要额外过滤。
- 连接外带数据缺少协议层标识:Pub/Sub、MONITOR 等场景依赖连接状态,普通响应与异步数据难以统一处理。
- 无法自然表达未知长度:大字符串或聚合结果必须先缓冲并计算长度,难以边产生边发送。
RESP3 的核心取舍是:不引入一个与 Redis 语义脱节的通用序列化库,而是在已有简单框架上直接加入 Redis 客户端真正需要的类型和通信语义。
| 能力 | RESP2 常见表现 | RESP3 表达 |
|---|---|---|
| 布尔值 | 整数 0/1 | Boolean:#f/#t |
| 浮点数 | 字符串 | Double:1.23 |
| 哈希/对象 | 扁平数组 | Map:%N |
| 集合 | 数组,客户端猜测 | Set:~N |
| 空值 | 两种 null 表示 | Null:_ |
| 异步事件 | 连接模式隐式切换 | Push:>N |
| 附加元数据 | 无统一机制 | Attribute:` |
| 二进制错误 | Simple Error 受 CRLF 限制 | Blob Error:!N |
4. RESP3 的线协议基础:字符、长度与 CRLF
所有协议单元都以类型前缀开始。除流式内容外,头部一般形如"类型字符 + 参数 + CRLF"。CRLF 是协议的一部分,不能用仅 LF 替代 。规范中为了阅读方便会把 CRLF 单独写成 <CR><LF>,真实 TCP 字节流中它是 0x0D 0x0A。
| 前缀 | 类型 | 头部形式 | 关键约束 |
|---|---|---|---|
+ |
Simple String | +text<CR><LF> |
内容不能包含 CR/LF |
- |
Simple Error | -CODE message<CR><LF> |
首个单词通常是错误码 |
: |
Number | :integer<CR><LF> |
有符号 64 位整数 |
, |
Double | ,floating<CR><LF> |
支持小数、指数、inf、-inf、nan |
_ |
Null | _<CR><LF> |
无负载 |
# |
Boolean | #t 或 #f<CR><LF> |
仅真/假 |
$ |
Blob String | $length<CR><LF>bytes<CR><LF> |
长度是字节数,不是字符数 |
! |
Blob Error | !length<CR><LF>bytes<CR><LF> |
错误内容二进制安全 |
= |
Verbatim String | =length<CR><LF>data<CR><LF> |
数据前缀含 txt: 或 mkd: |
( |
Big Number | (integer<CR><LF> |
任意精度整数文本 |
解析器必须按字节数读取 Blob 内容。例如 UTF-8 字符"中"占 3 个字节,不能把 Python/Java/Go 的字符串长度直接当作 RESP 长度。读取完 payload 后还必须消费末尾 CRLF,否则下一帧会从错误位置开始。
5. 11 类数据类型详解
5.1 Blob String(二进制安全字符串)
Blob String 是 Redis 命令参数和大多数普通回复的主力类型,格式为 $<length><CR><LF><bytes><CR><LF>。长度允许使用 64 位无符号范围,空字符串是 $0<CR><LF><CR><LF>;它与 Null 不同。
$11<CR><LF>
hello world<CR><LF>
$0<CR><LF>
<CR><LF>
_<CR><LF> (Null,不是空字符串)
5.2 Simple String 与 Simple Error
+OK<CR><LF>
-ERR unknown command<CR><LF>
Simple 类型节省空间,但不能承载 CR/LF,也不适合任意二进制内容。Simple Error 的首个空格前单词可作为错误码,客户端不应依赖完整错误文案做脆弱的模式匹配。
5.3 Number、Double、Boolean 与 Null
:1000<CR><LF>
,3.14159<CR><LF>
,1.2e+3<CR><LF>
#t<CR><LF>
#f<CR><LF>
_<CR><LF>
Double 允许 inf、-inf 和 nan。规范 1.4 起将 NaN 规范化为 nan,并禁止 -nan;但为了兼容旧 Redis/旧 libc,客户端最好对历史上可能出现的大写或带附加内容的 NaN 表示保持容错。
5.4 Blob Error
!21<CR><LF>
SYNTAX invalid syntax<CR><LF>
它与 Blob String 使用相同的长度 + 负载结构,因而错误消息可以包含任意字节;负载开头仍建议使用大写错误码。
5.5 Verbatim String 与 Big Number
Verbatim String 使用 = 前缀,负载开头的四个字节通常是 txt: 或 mkd:,用于提示后续内容是纯文本还是 Markdown。redis-cli 等交互式客户端可据此原样呈现 LATENCY DOCTOR 一类带换行的报告;普通客户端也可以把它当字符串返回。
=15<CR><LF>
txt:Some string<CR><LF>
(3492890328409238509324850943850943825024385<CR><LF>
Big Number 用于超出有符号 64 位范围的整数。支持大整数的语言应返回大整数;不支持的语言应至少以字符串返回,并尽量保留"这是大整数"的类型信息。
6. 聚合类型与递归解析
聚合类型的共同框架是:<type><number-of-elements><CR><LF>,后面紧跟 N 个完整 RESP 值。元素可以继续嵌套,因此解析器本质是一个递归下降函数,或者是带栈的迭代状态机。
| 前缀 | 类型 | 元素数量的含义 | 语义 |
|---|---|---|---|
* |
Array | 元素个数 | 有序序列 |
% |
Map | 键值对数量(后续值数量为 2N) | 无序映射 |
~ |
Set | 元素个数 | 无序集合 |
| ` | ` | Attribute | 键值对数量 |
> |
Push | 数组元素个数 | 服务器主动发送的带外消息 |
Array 示例:
*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]。解析时不能只根据"返回了几个值"判断业务含义;RESP3 的类型前缀已经表达了这种语义,客户端应尽量映射到语言惯用的 list、dict、set 等类型。
Map 示例:
%2<CR><LF>
+first<CR><LF>
:1<CR><LF>
+second<CR><LF>
:2<CR><LF>
Map 的 N 是键值对数而非元素总数,因此读取 2N 个 RESP 值。协议允许键和值是任意 RESP3 类型;工程实现可对键类型做限制,但不能因此破坏底层解析器。
Set 示例:
~5<CR><LF>
+orange<CR><LF>
+apple<CR><LF>
#t<CR><LF>
:100<CR><LF>
:999<CR><LF>
Set 无序,不能依赖线上顺序。规范不强制服务器绝不重复,客户端若映射到原生集合通常会自然去重;若映射为低级数组,则应保留类型标签。
7. 属性、推送与"请求-响应"模型的变化
7.1 Attribute:不改变主回复的附加信息
Attribute 使用 | 前缀,结构与 Map 相同。客户端读到它后,应把属性单独累积,继续读取紧随其后的主回复;属性不是主回复数组的一部分。
|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>
上述真正的命令结果仍是 [2039123, 9543892];属性描述键的流行度。属性可以出现在某个回复有效部分之前的任意位置,并只通知紧随其后的回复部分。客户端无需理解业务含义,但应提供 API 让调用方访问。
7.2 Push:可与普通回复交错的异步数据
Push 的结构类似 Array,但前缀是 >;第一个元素必须是字符串,用于说明推送类型。Redis Pub/Sub 的 message 推送示例:
>3<CR><LF>
+message<CR><LF>
+somechannel<CR><LF>
+this is the message<CR><LF>
Push 可能在任意两个顶层协议项之间到达,但不会嵌在 Map 等复合回复的中间。客户端读取循环必须区分"推送事件"和"命令回复":推送进入回调或事件队列;下一个非 Push 的顶层回复才对应队列中的下一个命令。
>3<CR><LF>
+message<CR><LF>
+somechannel<CR><LF>
+payload<CR><LF>
$9<CR><LF>
Get-Reply<CR><LF>
因此,异步客户端通常采用"写命令队列 + 单一读循环 + Push 分发 + 非 Push 回复匹配"的架构。多个线程直接从同一 socket 读取是危险的,会破坏帧边界和回复归属。
8. 流式字符串与流式聚合
RESP3 为未知长度的数据提供了标准化流式表示。它们在规范中很重要,但实际 Redis 普通命令未必会使用;客户端库仍应明确支持范围或清晰报"不支持"。
8.1 流式字符串
流式字符串以 $?<CR><LF> 开始,之后是若干个分块,每块为 ;<count><CR><LF> + count 字节 + CRLF,最后以 ;0<CR><LF> 结束。
$?<CR><LF>
;4<CR><LF>
Hell<CR><LF>
;5<CR><LF>
o wor<CR><LF>
;1<CR><LF>
d<CR><LF>
;0<CR><LF>
解析器不能把分块 CRLF 当作内容的一部分;每个块的 count 仍然按字节计算。与传统 Blob String 相比,流式编码无需预先知道总长度,适合大文件、复制快照或模块边生产边发送的结果。
8.2 流式聚合
Array、Set、Map 等聚合类型可以使用 ? 作为数量,结束时用单独的 End 类型 .<CR><LF>。Map 仍必须保证元素成对出现。
*?<CR><LF>
:1<CR><LF>
:2<CR><LF>
:3<CR><LF>
.<CR><LF>
%?<CR><LF>
+a<CR><LF>
:1<CR><LF>
+b<CR><LF>
:2<CR><LF>
.<CR><LF>
9. HELLO 握手、认证与兼容降级
新连接默认按 RESP2 模式工作。要切换到 RESP3,客户端发送 HELLO 3。HELLO 同时可以携带认证参数,并返回一个 Map,包含服务器与协议协商信息。
*4<CR><LF>
$5<CR><LF>
HELLO<CR><LF>
$1<CR><LF>
3<CR><LF>
$4<CR><LF>
AUTH<CR><LF>
$7<CR><LF>
default<CR><LF>
$8<CR><LF>
password<CR><LF>
实际使用中,客户端通常应先发送:
HELLO 3 AUTH default <password>
协议层命令本身仍按 RESP2 风格的数组编码发送,因为连接切换发生在 HELLO 成功之后。成功后,服务器后续回复可以使用 RESP3 类型;失败时连接通常仍停留在 RESP2 模式。
| 情况 | 典型结果 | 客户端动作 |
|---|---|---|
| 服务器支持 RESP3,认证成功 | HELLO 返回 Map | 进入 RESP3 解析模式 |
| 请求版本过高 | -NOPROTO ... |
尝试较低版本,如 RESP2 |
| 旧服务器不认识 HELLO | -ERR unknown command |
按 RESP2 发送 AUTH 或继续 RESP2 |
| 认证失败 | -ERR invalid password |
连接仍处于原模式,不要误切换 |
| Redis 响应字段变化 | Map 字段数量和值可能变化 | 按键读取,避免依赖固定顺序/固定字段数 |
Redis 的 HELLO 响应通常会提供 server、version、proto,以及 Redis 相关的 id、mode、role、modules 等信息;但规范明确提示字段集合可能演进,因此客户端应容忍未知字段。
10. 使用 redis-cli 进行实战观察
以下实验假设本机运行 Redis,并使用 redis-cli。不同 Redis 版本、集群模式或认证配置可能导致返回细节不同;实验重点是观察协议形状。
10.1 查看协议协商结果
redis-cli HELLO 3
#典型结果包含:server、version、proto、id、mode、role、modules
10.2 用 MONITOR 或抓包观察命令
redis-cli --raw MONITOR
# 在另一个终端执行:
redis-cli SET user:1 alice
redis-cli GET user:1
redis-cli 的展示模式会影响可读性。若要学习真实线协议,建议使用网络抓包工具、客户端库的 debug 日志,或编写一个仅用于实验的 TCP 读取器;不要把 redis-cli 的格式化输出误认为 RESP 原始字节。
10.3 观察不同 Redis 数据结构对应的 RESP3 语义
redis-cli -3 SET demo:value hello
redis-cli -3 GET demo:value
redis-cli -3 HSET demo:hash name redis version 7
redis-cli -3 HGETALL demo:hash
redis-cli -3 SMEMBERS demo:set
redis-cli -3 PING
重点观察:PING 常见为 Simple String;GET 常见为 Blob String 或 Null;HGETALL 在 RESP3 下可表达为 Map;SMEMBERS 可表达为 Set。具体命令与版本有关,应以实际服务器返回的首字节为准。
10.4 Pub/Sub 与 Push
# 订阅端
redis-cli -3 SUBSCRIBE news
# 发布端
redis-cli PUBLISH news "hello resp3"
在支持 RESP3 的客户端中,订阅通知应作为 Push 事件交给回调,而不是被错误地当作某个普通命令的返回值。
11. 编写 RESP3 客户端:编码、解析与异步事件
11.1 请求编码原则
Redis 命令可以统一编码成 Blob String 数组。编码器应以 UTF-8 或调用方指定的字节数组为输入,长度按字节计算。
def encode_command(*args):
parts = [f"*{len(args)}\r\n".encode()]
for arg in args:
b = arg if isinstance(arg, bytes) else str(arg).encode("utf-8")
parts += [f"${len(b)}\r\n".encode(), b, b"\r\n"]
return b"".join(parts)
encode_command("SET", "k", "值")
# b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$3\r\n值\r\n"
示例中的"值"在 UTF-8 中是 3 字节。生产实现还应限制参数大小、拒绝不必要的隐式类型转换,并避免将命令字符串拼接成带空格的单个参数。
11.2 解析器的最小状态机
一个健壮解析器至少需要以下状态:
READ_TYPE:读取一个字节,决定后续解析函数。
READ_LINE:持续读取直到 CRLF,解析数量、整数或类型头。
READ_BLOB:按字节数读取 payload,再严格校验末尾 CRLF。
READ_AGGREGATE:为固定长度聚合读取 N 个子值;Map 读取 2N 个。
READ_STREAM:对 $? 或 *? 进入分块/递归读取,直到 ;0 或 . 结束。
ATTACH_ATTRIBUTE:保存 Attribute,读取后续主值时绑定;顶层 Push 则立即分发。
ERROR/EOF:连接断开、长度非法、未知类型、超限或 CRLF 错误都要给出明确错误。
伪代码:
read_value():
t = read_exact(1)
if t in "+-,:_#": return read_simple(t)
if t in "$!=" : return read_blob_like(t)
if t == "(": return read_big_number()
if t in "*%~|>": return read_aggregate(t)
if t == ".": return END
raise ProtocolError("unknown RESP3 type")
read_aggregate(t):
n = read_line()
if n == "?": return read_stream_aggregate(t)
values = [read_value() for _ in range(n * 2 if t in "%|" else n)]
return construct(t, values)
11.3 同步客户端与异步客户端
同步客户端可以在每次命令后读取,直到得到一个非 Push 回复;读取期间遇到 Push 就回调或放入事件队列。异步客户端则应由一个 reader loop 独占 socket 读取权,再依据"命令序号"把非 Push 回复投递给 future/promise。
| 组件 | 职责 | 常见错误 |
|---|---|---|
| Writer | 按数组 + Blob String 编码命令 | 把字符数当字节数;命令参数注入 |
| Reader | 完整读取一帧并递归解析 | 一次 recv 就假设拿到完整回复 |
| Dispatcher | 区分 Push、Attribute、普通回复 | 把 Push 当作请求回复;丢失属性 |
| Connection state | 记录 RESP2/RESP3、认证、关闭状态 | HELLO 失败后仍按 RESP3 解码 |
| Limits | 限制深度、长度、元素数 | 恶意长度导致内存耗尽 |
12. 工程实践中的坑点、测试与安全
12.1 最容易踩的协议坑
TCP 没有消息边界:一次 recv 可能只有半个头,也可能包含多帧,必须使用缓冲区。
长度是字节:多字节 UTF-8、二进制 key、压缩内容都要按 bytes 计数。
Null 不等于空:_ 表示不存在/无值,$0 表示长度为 0 的 Blob String。
Map 的数量是键值对数:%2 后面是 4 个值。
Set 无序:不要在测试中断言返回顺序。
Attribute 不属于主回复:必须先读属性,再读真正回复。
Push 可插队:回复匹配逻辑不能简单地"写一个命令后读一个 RESP 值"。
不要固定依赖 HELLO Map 的字段数量或字段顺序。
对于流式类型,不要把 ? 当成普通整数;必须读到明确终止符。
12.2 推荐测试集
基础标量:+OK、-ERR、:0、:-1、,1.25、,inf、,nan、#t、#f、_。
Blob 边界:空串、1 字节、含 CR/LF 的二进制串、超长长度、UTF-8 多字节内容。
嵌套结构:数组套 Map、Map 套 Set、空聚合、深层嵌套。
错误输入:未知前缀、负长度、非法数字、缺少 CRLF、Map 奇数个元素。
异步交错:Push 在普通回复前、后、连续多个 Push。
流式编码:多个 chunk、空 chunk、最后 ;0、流式聚合终止 .。
兼容回退:HELLO 不存在、NOPROTO、认证失败以及协议版本选择。
资源安全:最大帧长度、最大嵌套深度、最大元素个数、读取超时和连接关闭。
12.3 安全与资源控制
RESP3 本身是序列化协议,不负责 TLS、身份授权或命令级权限。生产连接应结合 TLS、ACL、网络隔离、超时与连接池。解析器还必须防御"声明超大长度但迟迟不发送""极深嵌套""无限流式聚合"等输入导致的内存和 CPU 消耗。
13. 总结:如何把规范落到系统设计
学习 RESP3 可以分三层推进:
第一层:记住前缀与语义。尤其是 $、_、:、,、#、*、%、~、|、>。
第二层:理解递归帧。聚合类型不是字符串拼接,而是由 N 个完整 RESP 值组成;长度按字节,分隔符是 CRLF。
第三层:处理真实连接。HELLO 协商、RESP2 回退、Attribute、Push、半包/粘包、超时和资源上限,决定客户端能否在生产环境稳定运行。
RESP3 的价值不只是"让 Redis 返回更多类型",而是把数据语义和通信行为都显式写进协议:集合就是集合,映射就是映射;异步事件有 Push 标记;附加信息有 Attribute;未知长度数据有流式表示。这样客户端不必过度依赖命令名称、连接私有状态和脆弱的文本推断。
最后,实际开发应始终以服务器返回的首字节和完整帧结构为准,先写一个可独立测试的协议层,再在其上实现 Redis 命令 API。协议层越小、越严格、越少掺杂业务语义,后续支持新 Redis 命令和新 RESP3 类型就越容易。