【高阶篇】3.1 Redis协议(RESP )详解


在这里插入图片描述

0. 前言

当我们谈论 Redis 时,一般来说,我们讨论的核心是它用来存储和检索数据的多种数据结构:字符串、列表(Lists)、集合(Sets)、有序集合(Sorted Sets)和散列表(Hashes)等。但是,这些操作必须通过一定的协议来执行,这个协议就是Redis协议 (RESP, Redis Serialization Protocol)。

RESP 是一个简单而强大的协议,设计得非常易于理解和实现,几乎可以说是目前中间件中最简单的一种协议。这也是 Redis 受到广泛欢迎的一部分原因。这篇博客将详细介绍 RESP,包括其基本格式,以及如何使用 RESP 与 Redis 服务器进行交互。无论你是一个开发者,还是想了解更深层次的 Redis 工作原理的人,都可以从这篇文章get到一些点。

大纲

在这里插入图片描述

1. Redis协议(RESP)

1. 简介

Redis协议,也被称为 RESP (Redis Serialization Protocol),它是一种简单的文本协议,用于在客户端和服务器之间操作和传输数据。可以说是最简单的一种传输协议。

RESP 协议描述了不同类型的数据结构,并且定义了请求和响应之间如何以这些数据结构进行交互。

RESP 协议支持的数据类型:


  1. 简单字符串(Simple Strings): 以 “+” 开头,例如 “+OK\r\n” 表示一个成功的响应。
  2. 错误(Errors): 以 “-” 开头,例如 “-ERR unknown command\r\n” 表示一个错误响应。
  3. 整数(Integers): 以 “:” 开头,例如 “:1000\r\n” 表示整数1000。
  4. 批量字符串(Bulk Strings): 以 “$” 开头,例如 “$6\r\nfoobar\r\n” 表示一个长度为6的字符串 “foobar”。
  5. 数组(Arrays): 以 “*” 开头,例如 “*3\r\n:1\r\n:2\r\n:3\r\n” 表示包含3个整数的数组 [1, 2, 3]。

RESP 非常简单且人类可读,这使得 Redis 能够易于使用和调试。同时,RESP 也允许客户端和服务器以高效和低延迟的方式发送和接收数据

例如,一个 Redis 客户端发送一个 “SET mykey myvalue” 命令,将转换为 RESP 协议如下:

*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n

Redis 服务器将解析这个 RESP 请求,并返回一个 RESP 响应,如 “+OK\r\n”,表示命令成功执行。

2. 协议设计

  1. 简单字符串(Simple Strings):以"+“字符开头,例如:”+OK\r\n"。
  2. 错误信息(Errors):以"-“字符开头,例如:”-Error message\r\n"。
  3. 整数(Integers):以":“字符开头,例如:”:1000\r\n"。
  4. 块字符串(Bulk Strings):以"$“字符开头,后跟字符串长度和字符串内容,例如:”$6\r\nfoobar\r\n"。
  5. 数组(Arrays):以"*“字符开头,后跟数组的长度和数组元素,例如:”*3\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nhello\r\n"。

在RESP协议中,客户端向服务器发送命令请求,服务器接收并处理这些命令,然后返回一个响应。服务器能够一次处理多个命令,并且能够处理各种类型的数据,包括字符串、列表、集合、散列表等。

下面是一个实例,客户端发送一个GET mykey命令:

*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n

如果mykey存在且其值为myvalue,那么服务器将返回:

$7\r\nmyvalue\r\n

如果mykey不存在,那么服务器将返回:

$-1\r\n

RESP协议的设计使得Redis的客户端和服务器能够高效地进行通信。客户端和服务器都可以一次处理多个请求和响应,而不需要为每个请求/响应建立一个新的连接。这种设计使得Redis能够在处理大量数据时保持高性能和低延迟。

RESP 协议还有一个特点就是能够处理流式数据,这意味着客户端和服务器可以在数据发送和接收过程中开始处理数据,而不需要等待所有数据都发送或接收完毕。这对于处理大量数据或者大型集群非常有用。

在 RESP 协议中,一条命令或者响应的结束由 “\r\n” 标记。这种结构使得在数据流中识别消息的开始和结束变得非常简单。这也是 RESP 协议为什么能够做到在处理大量数据时仍然保持高效的一个重要原因。

另一个实用的特性是 RESP 协议对于二进制安全的支持。虽然 RESP 是一个基于文本的协议,但是它能够处理二进制数据。例如,你可以在 Redis 中存储一个图片或者视频文件,然后使用 RESP 协议来获取或者修改这些文件。

在处理二进制数据时,数据被当作一个批量字符串来处理。批量字符串的长度由一个数字表示,数字后面紧跟着的是字符串的内容。例如,表示一个长度为 5 的二进制数据 “hello”,在 RESP 协议中将会是 “$5\r\nhello\r\n”。

在 RESP 协议中,错误信息也是作为一种数据类型进行处理的。如果服务器在处理命令时遇到错误,那么它会返回一个以 “-” 开头的错误信息,像这样:“-ERR Unknown Command\r\n”。

总的来说,RESP 协议是一个非常强大且灵活的协议,能够适应各种不同类型的数据和应用场景。

附加类型

RESP协议的响应类型除了简单字符串、错误、整数、批量字符串和数组外,还有一种特殊的数据类型,那就是Null。在RESP协议中,Null值可以用于表示一个不存在或者未定义的值。

例如,当你尝试获取一个不存在的键的值时,Redis服务器会返回一个Null响应。在RESP协议中,Null响应可以是一个Null批量字符串($-1\r\n),也可以是一个Null数组(*-1\r\n)。

3. 数据传输

请求和响应之间的交互模式

RESP协议的数据传输采用简单的文本流,使用\r\n进行分隔符。
此外,RESP协议还定义了请求和响应之间的交互模式。在大多数情况下,交互模式是请求/响应模式,即客户端发送一个请求,然后服务器返回一个响应。然而,RESP协议也支持服务器推送模式(push messages),在这种模式下,服务器可以在没有接收到请求的情况下向客户端发送消息。这对于实现实时通信和事件驱动的应用场景非常有用。

客户端与服务端交互

客户端发送命令给Redis服务端时,将命令按RESP协议进行编码后发送。服务端接收到命令后进行解码,并根据命令进行相应操作后返回结果,也是按RESP协议进行编码后发送给客户端。

4. java实现 RESP协议

我用java 做了一个简单的实现,方便大家理解RESP协议。

package com.icepip.project.redis.resp;

import java.io.*;
import java.net.*;
/**
 * Redis协议 RESP 学习和用java 原生简单实现
 * 本代码示例 对应博客 《【高阶篇】Redis协议(RESP )详解》感谢大家指正
 * @author 冰点
 * @version 1.0.0
 * @date 2023/9/7 17:42
 */
public class RedisRESP {
    public static void main(String[] args) throws IOException {
        // 创建一个 socket 连接
        Socket socket = new Socket("127.0.0.1", 6379);

        // 获取输出流,用于向服务器发送命令
        BufferedWriter out = new BufferedWriter(
                new OutputStreamWriter(
                        socket.getOutputStream(), "UTF-8"));

        // 获取输入流,用于接收服务器的响应
        BufferedReader in = new BufferedReader(
                new InputStreamReader(
                        socket.getInputStream(), "UTF-8"));

     // TODO 如果 Redis 服务器需要密码进行身份验证 如果不需要密码 则去掉这块代码验证
        String password = "123456";
        if (!password.isEmpty()) {
            // 发送 AUTH 命令进行身份验证
            out.write("AUTH " + password + "\r\n");
            out.flush();

            // 读取服务器响应
            String response = in.readLine();
            System.out.println("响应:"+response);
            if (response.contains("OK")) {
                // 身份验证成功
                System.out.println("Authentication successful");
            } else {
                // 身份验证失败
                System.out.println("Authentication failed");
                // 进行错误处理或关闭连接等操作
                return;
            }
        }

        // 字符串数据类型
        // 使用 SET 命令设置一个键值对
        sendCommand(out, in, "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n");
        // 使用 GET 命令获取键的值
        sendCommand(out, in, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n");

        // 列表数据类型
        // 使用 RPUSH 命令将一个元素添加到列表的右侧
        sendCommand(out, in, "*3\r\n$5\r\nRPUSH\r\n$4\r\nlist\r\n$5\r\nvalue\r\n");
        // 使用 LPOP 命令移除并获取列表的第一个元素
        sendCommand(out, in, "*2\r\n$4\r\nLPOP\r\n$4\r\nlist\r\n");

        // 集合数据类型
        // 使用 SADD 命令添加一个元素到集合
        sendCommand(out, in, "*3\r\n$4\r\nSADD\r\n$3\r\nset\r\n$5\r\nvalue\r\n");
        // 使用 SPOP 命令随机移除并返回一个元素
        sendCommand(out, in, "*2\r\n$4\r\nSPOP\r\n$3\r\nset\r\n");

        // 有序集合数据类型
        // 使用 ZADD 命令添加一个元素到有序集合,为元素设置分数为0
        sendCommand(out, in, "*4\r\n$4\r\nZADD\r\n$4\r\nzset\r\n$1\r\n0\r\n$5\r\nvalue\r\n");
        // 使用 ZRANGE 命令返回有序集合中所有元素,按分数从小到大排序
        sendCommand(out, in, "*4\r\n$6\r\nZRANGE\r\n$4\r\nzset\r\n$1\r\n0\r\n$2\r\n-1\r\n");

        // 散列表数据类型
        // 使用 HSET 命令向散列表添加一个键值对
        sendCommand(out, in, "*4\r\n$4\r\nHSET\r\n$4\r\nhash\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");
        // 使用 HGET 命令获取散列表中指定字段的值
        sendCommand(out, in, "*3\r\n$4\r\nHGET\r\n$4\r\nhash\r\n$5\r\nfield\r\n");

        // 关闭连接
        in.close();
        out.close();
        socket.close();
    }


    /**
     *  辅助方法,用于发送命令并打印响应
     * @param out
     * @param in
     * @param command
     * @throws IOException
     */
    private static void sendCommand(BufferedWriter out, BufferedReader in, String command) throws IOException {
        System.out.println("发送:"+command);
        out.write(command);    // 向服务器发送命令
        out.flush();           // 清空缓冲区,确保命令立即发送
        System.out.println("接收:"+in.readLine()); // 读取并打印服务器的响应
    }
}

输出如下。里面一些转义字符 被IDEA控制台直接执行了。所以看到的是换行。但不影响大家理解。
在这里插入图片描述

3. 总结

Redis 协议主要分为 16 种,其中 8 种协议对应 8 种数据类型,你选择了使用什么数据类型,就使用对应的响应操作指令即可。剩下

8 种协议如下所示。

  1. 发布订阅协议(Pub-Sub protocol):此协议允许客户端订阅一个或多个频道(channels)以接收服务器发布的消息。这对于实现实时的事件驱动应用场景非常有用。

  2. 事务协议(Transaction protocol):Redis提供了MULTI, EXEC, DISCARDWATCH等事务命令来实现事务功能。当在MULTIEXEC之间的一系列命令作为一个原子操作被执行。

  3. 脚本协议(Scripting protocol):使用EVAL命令,你可以执行Lua脚本。此外,EVALSHA命令可以执行先前通过SCRIPT LOAD命令加载的脚本。

  4. 连接协议(Connection protocol):可以通过AUTH, SELECT, QUIT等命令进行身份验证,切换数据库,关闭连接等。

  5. 复制协议(Replication protocol)SLAVEOFSYNC命令用于控制和实现Redis的主从复制功能。

  6. 配置协议(Configuration protocol)CONFIG GET, CONFIG SET等命令可以用来获取和设置Redis服务器的配置参数。

  7. 调试统计(Debugging and statistics protocol):包括MONITOR, INFO, SLOWLOG等命令,这些命令提供调试和统计信息。

  8. 其他内部命令(Other internal commands):如DUMP, RESTORE, MIGRATE等用于数据迁移和持久化等操作。

4.参考资料

  1. Redis官方文档:https://redis.io/
  2. Redis实战(书籍)
  3. Redis设计与实现(书籍)
  4. https://www.toutiao.com/article/7088132873051488780/
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis 是一个开源的内存数据存储系统,主要用作缓存数据库Redis 配置文件是 Redis 服务器的配置文件,它包含了 Redis 服务器的所有配置选项。 Redis 配置文件的位置在 Redis 安装目录下的 redis.conf 文件中,可以通过修改该文件来配置 Redis 服务器。下面是 Redis 配置文件的一些常用配置选项的详解: 1. bind:Redis 服务器的绑定地址,在默认情况下,Redis 服务器会绑定所有可用的网络接口,可以通过设置 bind 选项来指定 Redis 服务器的绑定地址。 2. port:Redis 服务器的监听端口,默认情况下,Redis 服务器会监听 6379 端口,可以通过设置 port 选项来指定 Redis 服务器的监听端口。 3. daemonize:Redis 服务器是否以守护进程的方式启动,默认情况下,Redis 服务器会以前台进程的方式启动,可以通过设置 daemonize 选项来指定 Redis 服务器是否以守护进程的方式启动。 4. logfile:Redis 服务器的日志文件路径,默认情况下,Redis 服务器的日志文件路径为标准输出,可以通过设置 logfile 选项来指定 Redis 服务器的日志文件路径。 5. databases:Redis 服务器的数据库数量,默认情况下,Redis 服务器只有一个数据库,可以通过设置 databases 选项来指定 Redis 服务器的数据库数量。 6. maxclients:Redis 服务器的最大连接数,默认情况下,Redis 服务器的最大连接数为 10000,可以通过设置 maxclients 选项来指定 Redis 服务器的最大连接数。 7. maxmemory:Redis 服务器的最大内存使用量,默认情况下,Redis 服务器不限制最大内存使用量,可以通过设置 maxmemory 选项来指定 Redis 服务器的最大内存使用量。 以上是 Redis 配置文件的一些常用配置选项的详解,通过修改这些配置选项,可以对 Redis 服务器进行各种配置和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰点.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值