强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

01 - RESP 协议概述

RESP 协议概述

1.1 什么是 RESP

RESP(REdis Serialization Protocol)是 Redis 客户端与服务器之间的通信协议。它由 Salvatore Sanfilippo(antirez)在 2009 年设计,目标是:

  • 实现简单:解析器可以用几十行代码完成
  • 人类可读:可以用 telnet 直接调试
  • 高效传输:二进制安全,支持大体积数据

协议定位

┌──────────────┐         RESP          ┌──────────────┐
│   Client     │ ─────────────────────→ │   Redis      │
│   (Application)│ ←───────────────────── │   Server     │
└──────────────┘      TCP Stream       └──────────────┘

RESP 运行在 TCP 之上,默认端口 6379。每个 Redis 命令对应一次请求-响应交互:

  1. 客户端将命令编码为 RESP 数组发送
  2. 服务器解析命令并执行
  3. 服务器将结果编码为 RESP 类型返回

核心特征

特征说明
传输层TCP(也支持 Unix Domain Socket)
编码方式文本前缀 + CRLF 分隔,二进制安全
类型系统RESP2 有 5 种,RESP3 扩展到约 14 种
方向性请求和响应使用相同的类型系统
状态无状态协议(单个连接内保持顺序)

1.2 版本演进

RESP2(Redis 1.2+,正式成为默认协议)

RESP2 是 Redis 长期使用的协议版本,定义了 5 种基本类型:

类型前缀用途
Simple String+OK 等简短文本响应
Error-错误信息
Integer:整数计数
Bulk String$二进制安全的字符串(含 NULL)
Array*命令和复合响应的载体

RESP2 的设计极度精简——整个协议规范不到一页纸。这种简洁性是 Redis 生态繁荣的重要原因:几乎所有语言都有轻量级的 Redis 客户端实现。

RESP3(Redis 6.0+,2020 年 4 月)

RESP3 由 antirez 重新设计,目标是解决 RESP2 的几个痛点:

RESP2 的问题:

  1. 类型信息不足HGETALL 返回扁平数组,客户端必须"知道"这是 Map
  2. NULL 表示不一致:空字符串和 NULL 都用 $-1\r\n 表示
  3. 缺少布尔/双精度:需要客户端自行转换
  4. 属性推送困难:服务器无法附带元数据(如耗时统计)

RESP3 的改进:

新类型前缀解决的问题
Null_统一的 NULL 表示
Boolean#原生布尔值
Double,浮点数
Big Number(超出 64 位的整数
Verbatim String=带编码提示的字符串
Map%键值对集合
Set~无序集合
Attribute|元数据(不影响结果)
Push>服务器主动推送

版本选择

# 查看当前协议版本
redis-cli INFO clients
# connected_clients:1
# ...
# resp:2

# 使用 RESP3(需要 Redis 6.0+)
redis-cli --resp3
# 或在连接时发送 HELLO 3

客户端切换到 RESP3 的协议握手:

→ HELLO 3
← %7
← $6
← server
← $5
← redis
← $7
← version
← $5
← 7.2.4
← ...

1.3 设计哲学

哲学一:面向文本优先(Text-Oriented)

RESP 的类型标识使用 ASCII 字符(+, -, :, $, *),整数和长度用十进制数字表示。这使得人类可以直接用 telnet 与 Redis 交互:

$ telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

*3\r\n
$3\r\n
SET\r\n
$5\r\n
hello\r\n
$5\r\n
world\r\n
+OK

这种"人类可调试"的特性极大地降低了开发和排错成本。

哲学二:二进制安全(Binary-Safe)

虽然类型标识是文本,但 Bulk String 的内容可以包含任意字节。长度前缀($<length>\r\n)确保解析器不会被内容中的 \r\n 截断:

$11\r\n
hello\r\nworld\r\n

这个 Bulk String 包含 hello\r\nworld(11 字节),解析器通过长度字段精确读取,不会误判。

哲学三:递归组合(Recursive Composition)

RESP 的核心思想是:数组可以嵌套。一个命令是"字符串的数组",一个复杂响应是"数组的数组"。这种递归结构足够表达几乎所有数据类型:

# 命令: SET key value
*3          ← 外层数组(3 个元素)
$3          ← 元素 1:Bulk String(3 字节)
SET
$3          ← 元素 2:Bulk String
key
$5          ← 元素 3:Bulk String
value

哲学四:流式解析(Streaming-Friendly)

RESP 的设计天然支持流式处理:每个数据帧的前几个字节就告知了类型和长度,解析器无需等待完整数据即可开始处理。这对于 Pipeline 和大体积响应尤为重要。


1.4 与 Memcached 协议对比

Memcached 也是广泛使用的键值存储,其文本协议与 RESP 有显著差异:

协议格式对比

维度RESP (Redis)Memcached Text Protocol
分隔符CRLF (\r\n)CRLF (\r\n)
命令格式结构化数组空格分隔的文本行
响应类型5+ 种带前缀类型关键字开头(VALUE, STORED, END
二进制安全✅ Bulk String 长度前缀❌ 文本协议不保证
多行响应用长度前缀声明END 标记结束
管道支持原生支持部分版本支持
协议升级RESP2 → RESP3文本 → 二进制协议

命令对比示例

存储操作:

# Memcached
set key 0 3600 5\r\n
hello\r\n
STORED\r\n

# Redis RESP
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nhello\r\n
+OK\r\n

读取操作:

# Memcached
get key\r\n
VALUE key 0 5\r\n
hello\r\n
END\r\n

# Redis RESP
*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n
$5\r\n
hello\r\n

二进制安全问题

Memcached 文本协议的一个重大缺陷是不支持二进制安全。如果你的值包含 \r\n,文本协议会出错。Memcached 的二进制协议(Binary Protocol)解决了这个问题,但增加了复杂性。

RESP 通过 Bulk String 的长度前缀优雅地解决了这个问题:解析器先读取长度,再精确读取对应字节数,内容中的任何字节都不会导致误解析。

性能对比

指标RESPMemcached Text
解析复杂度O(n),按长度跳过O(n),需扫描分隔符
Pipeline 效率原生支持,一次发送多命令需要特殊处理
网络开销较小(类型前缀很短)类似
扩展性丰富数据结构纯键值对

1.5 快速上手:用 telnet 理解协议

最直观的学习方式是用 telnet 手动发送 RESP 数据:

# 终端 1:启动 Redis
redis-server

# 终端 2:用 telnet 连接
telnet 127.0.0.1 6379

发送 PING

*1\r\n
$4\r\n
PING\r\n

服务器响应:

+PONG\r\n
### 发送 SET/GET

*3\r\n $3\r\n SET\r\n $7\r\n mykey\r\n $5\r\n hello\r\n


响应:`+OK\r\n`

*2\r\n $3\r\n GET\r\n $7\r\n mykey\r\n


响应:`$5\r\nhello\r\n`

### 观察错误

*2\r\n $3\r\n GET\r\n $3\r\n nosuch\r\n


响应:`$-1\r\n`(NULL Bulk String,表示 key 不存在)

---

## 1.6 工程中的协议层

在实际的 Redis 客户端库中,协议处理通常分为两层:

┌─────────────────────────────────┐ │ Application Layer │ │ redis.get(“key”) │ ├─────────────────────────────────┤ │ Command Layer │ │ [“GET”, “key”] │ ├─────────────────────────────────┤ │ Protocol Layer │ │ *2\r\n$3\r\nGET\r\n$3\r\nkey\r\n │ ├─────────────────────────────────┤ │ Transport Layer │ │ TCP / Unix Socket │ └─────────────────────────────────┘


理解这个分层有助于定位问题:
- 命令格式错误 → 检查 Command Layer
- 数据解析失败 → 检查 Protocol Layer
- 连接超时 → 检查 Transport Layer

---

## 1.7 注意事项

> **⚠️ CRLF 是底线**
> RESP 协议的行终结符严格使用 `\r\n`(0x0D 0x0A),不是 `\n`。手动构造协议时最容易犯的错误就是只发送 `\n`。

> **⚠️ 长度是字节数,不是字符数**
> `$5\r\nhello\r\n` 中的 5 是字节长度。对于中文等多字节字符,必须按 UTF-8 编码后的字节数计算。

> **⚠️ 协议版本兼容**
> RESP3 向下兼容 RESP2,但服务器必须在 HELLO 握手后才切换。未发送 HELLO 的连接默认使用 RESP2。

---

## 1.8 业务场景

### 场景一:微服务间的轻量消息

由于 RESP 是人类可读的文本协议,在调试阶段可以直接用 tcpdump 抓包查看 Redis 通信内容,比 Protobuf 等二进制协议更友好。

### 场景二:协议网关

如果你需要在 Redis 和其他存储(如 Memcached)之间做代理,理解两套协议的差异是实现转换层的前提。

### 场景三:嵌入式脚本

Redis 的 Lua 脚本引擎通过 RESP 协议与外部通信。理解协议可以让你写出更高效的脚本(减少数据转换开销)。

---

## 1.9 扩展阅读

| 资源 | 链接 |
|------|------|
| RESP2 规范 | [Redis Protocol Specification](https://redis.io/docs/latest/develop/reference/protocol-spec/) |
| RESP3 规范 | [RESP3 Specification](http://antirez.com/news/134) |
| antirez 博客:RESP3 动机 | [RESP3, a new Redis protocol](http://antirez.com/news/134) |
| Redis 源码:协议解析 | `networking.c` → `processInputBuffer()` |
| Memcached 协议 | [Memcached Protocol Wiki](https://github.com/memcached/memcached/wiki/Protocols) |

---

下一章:[RESP2 格式详解](../02-resp2/)