RESP3 不是 JSON:14 行类型表、HELLO 握手与客户端缓存
RESP(REdis Serialization Protocol)是 Redis 客户端与服务器之间的线缆协议(wire protocol)。虽然是为 Redis 设计的,也可以用于其他 C/S 软件。它序列化整数、字符串、数组等类型,另外有专门的错误类型。
版本演进:
- Redis 1.2 引入第一版
- Redis 2.0 起 RESP2 成为标准通信方式
- Redis 6.0 引入 RESP3,实验性 opt-in 支持,包含 HELLO 命令做握手与协议版本升级
在 Redis 7 及以前,RESP2 和 RESP3 客户端都能调用全部核心命令,但同一命令在不同协议版本下可能返回不同类型的回复。官方说法是未来版本可能改变默认协议版本,但 RESP2 不太可能完全废弃;新特性可能要求 RESP3。协议名称就叫 RESP3,不是 RESP v3。
协议描述
RESP 本质是支持多种数据类型的序列化协议,第一个字节决定类型。它是二进制协议,控制序列使用标准 ASCII 编码:'A' 是字节 65,CR(\r)=13、LF(\n)=10、SP=32。
客户端把命令作为 bulk strings 组成的数组发给服务器:数组第一个(有时第二个)bulk string 是命令名,其余元素是参数。服务器按命令实现和客户端协议版本回复一个 RESP 类型。
| 类型 | 最低版本 | 分类 | 首字节 |
|---|---|---|---|
| Simple strings | RESP2 | Simple | + |
| Simple Errors | RESP2 | Simple | - |
| Integers | RESP2 | Simple | : |
| Bulk strings | RESP2 | Aggregate | $ |
| Arrays | RESP2 | Aggregate | * |
| Nulls | RESP3 | Simple | _ |
| Booleans | RESP3 | Simple | # |
| Doubles | RESP3 | Simple | , |
| Big numbers | RESP3 | Simple | ( |
| Bulk errors | RESP3 | Aggregate | ! |
| Verbatim strings | RESP3 | Aggregate | = |
| Maps | RESP3 | Aggregate | % |
| Sets | RESP3 | Aggregate | ~ |
| Pushes | RESP3 | Aggregate | > |
RESP2 只有 5 种:+ - : $ *。RESP2 用 *-1 和 $-1 表示 null,用 $ 表示浮点数和布尔,用数组表示一切集合(List/Hash/Set/ZSet),客户端必须知道当前命令才能把数组还原成正确类型,这是 RESP2 的主要痛点。
RESP3 新增:Null(_)、Boolean(#)、Double(,)、Big numbers(()、Bulk errors(!)、Verbatim strings(=)、Map(%)、Set(~)、Attribute(|)、Push(>)、Stream。使用 \r\n 作为分隔符,每个编码后跟 CRLF。
几个类型的具体行为:
- Verbatim string:以
=开头,后面 3 个字节表示格式(如txt),再接长度和内容,原样显示给用户。 - Map:以
%开头,后面是键值对数量。HGETALL在 RESP3 下直接返回 Map,客户端不用再做奇偶下标配对。 - Set:以
~开头,SMEMBERS返回 Set 类型。 - Push:首字节是
>,形如 Array,但第一个元素总是字符串,表示推送类型(如pubsub、monitor),用于 Pub/Sub、客户端缓存失效通知这类带外数据。客户端必须检查首元素来识别,处理完 push 后还需继续读取自己的 reply。 - Streamed strings / streamed aggregate:以块编码方式发送长度未知的字符串;聚合类型(Array/Set/Map)也可不指定长度,用显式终止符结束传输。
客户端握手
新的 RESP 连接应以 HELLO 命令开始会话,做两件事:协商协议版本,返回服务器信息。
HELLO [protover [AUTH username password] [SETNAME clientname]]
第一个参数是期望的协议版本;默认连接从 RESP2 开始。若指定版本过高或不被支持,服务器回 -NOPROTO 错误。
HELLO 3
HELLO 成功回复是一个 Map。所有 RESP3 实现都必须给出的字段:
server:"redis"或其他软件名version:服务器版本proto:服务器支持的最高 RESP 版本
Redis 实现里还会给出 id(连接标识)、mode(standalone/sentinel/cluster)、role(master/replica)、modules(已加载模块数组)。
为什么 RESP3 是为了客户端缓存
RESP3 的设计动因之一是客户端缓存(client-side caching):本地缓存子集降低延迟,但本地缓存与 Redis 数据之间需要失效通知。RESP2 缺少带外推送能力,很难做,于是 antirez 设计了下一代协议 RESP3。Push 类型让服务端能主动推送失效通知,客户端不需要轮询。
兼容性与落地
antirez 最初想 Redis 6 直接只支持 RESP3、不兼容 RESP2(文章 Why RESP3 will be the only protocol supported by Redis 6)。后来 Marc Gravell 提议用协商方式确定版本,才有了 HELLO 命令。最终 RESP3 向前兼容 RESP2,客户端升级后仍能访问旧服务。
Redis 请求统一使用 bulk strings 数组格式,整数/错误/简单字符串/null 只在响应里出现;只有客户端解析响应时才会遇到多种类型。
Kvrocks 案例:2023 社区 Roadmap 有用户希望支持 RESP3,权衡工作量与收益后没有推进。它已实现带外数据类型的读取,但对返回数据仍按 RESP2 处理。有实现者写了 RESP3 的 Go Reader/Value 编码解码(colobu),其中几点需要注意:编码时先读属性,属性是 Map 类型,数量是键值对数量;复杂类型递归编码;读取 Value 时要先检查是否有 Attribute 前缀。