RESP

Redis Serialization Protocol

Redis客户端与服务器之间通信的标准协议 THE STANDARD PROTOCOL

协议概述

定义与发展

RESP是一种二进制安全的文本协议,专为Redis设计,用于客户端与Redis服务器之间的通信。

  • 在Redis 1.2版本中首次引入
  • 在Redis 2.0版本中正式成为与Redis服务端通信的标准方案
  • Redis 6.0引入了RESP3版本,提供更丰富的数据类型和性能改进
RESP

Redis Serialization Protocol

Redis序列化协议

设计特点

  • 简单性

    采用文本协议,易于理解和实现

  • 高效性

    二进制安全,支持高效数据传输

  • 易于分析

    协议格式简单明确,易于语法分析

  • 可读性

    具有较好的人类可读性,便于调试

协议数据类型

基本数据类型

RESP本质上是一种序列化协议,支持多种数据类型,RESP中数据的第一个字节决定了其类型:

+

简单字符串 (Simple Strings)

以"+"开头,后跟字符串内容和"\r\n"结尾

-

错误 (Errors)

以"-"开头,后跟错误信息和"\r\n"结尾

:

整数 (Integers)

以":"开头,后跟整数值和"\r\n"结尾

$

大块字符串 (Bulk Strings)

以"$"开头,后跟字符串长度、"\r\n"、字符串内容和"\r\n"

*

数组 (Arrays)

以"*"开头,后跟数组长度、"\r\n"和数组元素内容

RESP3的扩展数据类型

RESP3协议在RESP2的基础上增加了更多的数据类型:

RESP3 主要扩展

  • Map类型
  • Set类型
  • 浮点数类型
  • Boolean类型
  • NULL和空值的区分
  • 大数值支持

协议工作原理

通信流程

1

客户端按RESP协议格式编码命令

将Redis命令转换为RESP格式的数据

2

客户端发送请求到Redis服务器

通过TCP连接发送RESP格式的请求

3

服务器解析RESP请求

Redis服务器解析接收到的RESP格式数据

4

服务器执行命令

根据解析结果执行相应的Redis命令

5

服务器按RESP协议格式编码响应

将执行结果转换为RESP格式的数据

6

服务器返回响应到客户端

通过TCP连接发送RESP格式的响应

7

客户端解析RESP响应

解析接收到的RESP格式数据并返回结果

请求-响应模型

请求过程

  • 客户端以RESP格式的字符串数组形式向Redis服务器发送命令
  • 每个命令都按照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

命令编码示例

SET key value 命令的RESP编码

*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"

GET key 命令的RESP编码

*2\r\n           # 数组,包含2个元素
$3\r\n           # 第一个元素长度为3
GET\r\n          # 第一个元素内容为"GET"
$3\r\n           # 第二个元素长度为3
key\r\n          # 第二个元素内容为"key"

编码过程可视化

协议实现原理

解析流程

  1. 1

    接收数据流

    从网络连接中读取原始数据

  2. 2

    检查首字节,确定数据类型

    • "+":解析简单字符串
    • "-":解析错误信息
    • ":":解析整数值
    • "$":解析大块字符串
    • "*":解析数组
  3. 3

    根据数据类型进行相应的解析处理

    针对不同类型的数据采用不同的解析策略

  4. 4

    对于数组类型,递归解析每个元素

    数组中的每个元素可能是任意RESP数据类型,需要递归处理

编码实现示例

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)
    }
}

协议应用场景

Redis客户端开发

RESP协议是开发Redis客户端的基础,所有Redis客户端都必须实现此协议

  • 各种编程语言的Redis客户端库(如Java的Jedis、Python的redis-py等)都实现了RESP协议
  • 命令行工具redis-cli通过RESP协议与Redis服务器通信

第三方兼容实现

RESP协议也被其他符合Redis协议的系统采用

  • Valkey(Redis的一个分支)采用RESP协议
  • Amazon ElastiCache for Redis支持RESP协议
  • GLIDE for Redis(AWS赞助的OSS Redis客户端)适用于任何符合RESP规范的Redis发行版

网络框架集成

一些流行的网络编程框架集成了RESP协议的实现

  • Netty框架中包含了对RESP协议的实现,便于开发高性能的Redis客户端
  • 通过序列化操作,将消息对象转换为符合RESP协议的字节数组,然后输出到ByteBuf中

RESP2 与 RESP3 的区别

主要改进

数据类型扩展

  • 增加了Map、Set、Boolean等类型
  • 支持浮点数类型
  • 区分NULL和空值

性能优化

  • 更高效的数据编码方式
  • 更丰富的客户端/服务器交互能力

兼容性

  • RESP3是向后兼容的,Redis服务器可以同时处理RESP2和RESP3协议
  • 客户端可以选择使用哪个版本的协议

注意事项

潜在问题

  • SpringBoot 2.5.7使用的lettuce版本6.1.5,连接Redis时默认通过RESP3协议,可能导致连接失败,需要手动改回RESP2

  • 在升级Redis实例版本时,尤其是升级到Redis 7.2版本,需要注意RESP协议的变化

协议的优缺点

优点

  • 1

    简单高效

    协议设计简单明确,易于实现和解析

  • 2

    二进制安全

    可以处理包含任意二进制数据的字符串

  • 3

    人类可读

    协议格式具有较好的可读性,便于调试

  • 4

    跨语言支持

    易于在各种编程语言中实现

  • 5

    高效性能

    简单的协议设计带来高效的网络通信性能

局限性

  • 1

    冗余

    某些数据类型的表示相对冗长,如大型数组或嵌套结构

  • 2

    类型有限

    RESP2只支持有限的数据类型,虽然RESP3已经扩展了类型系统

  • 3

    错误处理

    错误处理机制相对简单,不如某些现代协议丰富

实现RESP协议解析器的关键步骤

1

读取第一个字节

根据首字节确定数据类型

2

解析长度信息

对于大块字符串和数组,需要解析长度信息

3

递归解析

对于数组类型,需要递归解析每个元素

4

处理特殊值

处理NULL值和空值的特殊表示

5

错误处理

妥善处理协议解析过程中的各种异常情况

总结

RESP协议是Redis客户端与服务器通信的核心协议,其简单高效的设计理念使其成为Redis生态系统的重要组成部分。通过本文的详细讲解,您应该已经了解了RESP协议的定义、数据类型、编码规则、工作原理以及应用场景。

随着Redis版本的不断发展,RESP协议也在不断演进,从RESP2到RESP3,增加了更多的数据类型和性能优化。无论是开发Redis客户端、设计兼容Redis的系统,还是理解Redis的网络通信原理,深入理解RESP协议都是不可或缺的一环。

作为一个二进制安全的文本协议,RESP协议成功平衡了简单性、可读性和高效性,这也是它能够成为Redis标准通信协议的重要原因。