RESP是一种二进制安全的文本协议,专为Redis设计,用于客户端与Redis服务器之间的通信。
Redis Serialization Protocol
Redis序列化协议
采用文本协议,易于理解和实现
二进制安全,支持高效数据传输
协议格式简单明确,易于语法分析
具有较好的人类可读性,便于调试
RESP本质上是一种序列化协议,支持多种数据类型,RESP中数据的第一个字节决定了其类型:
以"+"开头,后跟字符串内容和"\r\n"结尾
以"-"开头,后跟错误信息和"\r\n"结尾
以":"开头,后跟整数值和"\r\n"结尾
以"$"开头,后跟字符串长度、"\r\n"、字符串内容和"\r\n"
以"*"开头,后跟数组长度、"\r\n"和数组元素内容
RESP3协议在RESP2的基础上增加了更多的数据类型:
将Redis命令转换为RESP格式的数据
通过TCP连接发送RESP格式的请求
Redis服务器解析接收到的RESP格式数据
根据解析结果执行相应的Redis命令
将执行结果转换为RESP格式的数据
通过TCP连接发送RESP格式的响应
解析接收到的RESP格式数据并返回结果
+OK\r\n
-ERR unknown command 'foobar'\r\n
:1000\r\n
$6\r\nfoobar\r\n
表示长度为6的字符串"foobar"
空字符串表示为:$0\r\n\r\n
NULL值表示为:$-1\r\n
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
表示一个包含3个元素的数组,分别是"SET"、"key"和"value"
空数组表示为:*0\r\n
NULL数组表示为:*-1\r\n
*3\r\n # 数组,包含3个元素
$3\r\n # 第一个元素长度为3
SET\r\n # 第一个元素内容为"SET"
$3\r\n # 第二个元素长度为3
key\r\n # 第二个元素内容为"key"
$5\r\n # 第三个元素长度为5
value\r\n # 第三个元素内容为"value"
*2\r\n # 数组,包含2个元素
$3\r\n # 第一个元素长度为3
GET\r\n # 第一个元素内容为"GET"
$3\r\n # 第二个元素长度为3
key\r\n # 第二个元素内容为"key"
从网络连接中读取原始数据
针对不同类型的数据采用不同的解析策略
数组中的每个元素可能是任意RESP数据类型,需要递归处理
// 伪代码:RESP协议解析器的实现思路
func parseRESP(reader *bufio.Reader) (interface{}, error) {
// 读取第一个字节,确定数据类型
firstByte, err := reader.ReadByte()
if err != nil {
return nil, err
}
switch firstByte {
case '+': // 简单字符串
line, err := readLine(reader)
return string(line), err
case '-': // 错误信息
line, err := readLine(reader)
return Error(string(line)), err
case ':': // 整数
line, err := readLine(reader)
if err != nil {
return nil, err
}
return strconv.ParseInt(string(line), 10, 64)
case '$': // 大块字符串
line, err := readLine(reader)
if err != nil {
return nil, err
}
length, err := strconv.ParseInt(string(line), 10, 64)
if err != nil {
return nil, err
}
if length < 0 {
return nil, nil // NULL值
}
data := make([]byte, length)
_, err = io.ReadFull(reader, data)
if err != nil {
return nil, err
}
// 读取末尾的\r\n
_, err = reader.ReadByte()
_, err = reader.ReadByte()
return data, nil
case '*': // 数组
line, err := readLine(reader)
if err != nil {
return nil, err
}
count, err := strconv.ParseInt(string(line), 10, 64)
if err != nil {
return nil, err
}
if count < 0 {
return nil, nil // NULL数组
}
array := make([]interface{}, count)
for i := int64(0); i < count; i++ {
value, err := parseRESP(reader)
if err != nil {
return nil, err
}
array[i] = value
}
return array, nil
default:
return nil, errors.New("未知的RESP数据类型")
}
}
// 读取一行,直到遇到\r\n
func readLine(reader *bufio.Reader) ([]byte, error) {
var line []byte
for {
b, err := reader.ReadByte()
if err != nil {
return nil, err
}
if b == '\r' {
b, err = reader.ReadByte()
if err != nil {
return nil, err
}
if b == '\n' {
return line, nil
}
}
line = append(line, b)
}
}
RESP协议是开发Redis客户端的基础,所有Redis客户端都必须实现此协议
RESP协议也被其他符合Redis协议的系统采用
一些流行的网络编程框架集成了RESP协议的实现
SpringBoot 2.5.7使用的lettuce版本6.1.5,连接Redis时默认通过RESP3协议,可能导致连接失败,需要手动改回RESP2
在升级Redis实例版本时,尤其是升级到Redis 7.2版本,需要注意RESP协议的变化
协议设计简单明确,易于实现和解析
可以处理包含任意二进制数据的字符串
协议格式具有较好的可读性,便于调试
易于在各种编程语言中实现
简单的协议设计带来高效的网络通信性能
某些数据类型的表示相对冗长,如大型数组或嵌套结构
RESP2只支持有限的数据类型,虽然RESP3已经扩展了类型系统
错误处理机制相对简单,不如某些现代协议丰富
根据首字节确定数据类型
对于大块字符串和数组,需要解析长度信息
对于数组类型,需要递归解析每个元素
处理NULL值和空值的特殊表示
妥善处理协议解析过程中的各种异常情况
RESP协议是Redis客户端与服务器通信的核心协议,其简单高效的设计理念使其成为Redis生态系统的重要组成部分。通过本文的详细讲解,您应该已经了解了RESP协议的定义、数据类型、编码规则、工作原理以及应用场景。
随着Redis版本的不断发展,RESP协议也在不断演进,从RESP2到RESP3,增加了更多的数据类型和性能优化。无论是开发Redis客户端、设计兼容Redis的系统,还是理解Redis的网络通信原理,深入理解RESP协议都是不可或缺的一环。
作为一个二进制安全的文本协议,RESP协议成功平衡了简单性、可读性和高效性,这也是它能够成为Redis标准通信协议的重要原因。