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

news2024/9/22 1:52:12

文章目录

  • 0. 前言
    • 大纲
  • 1. Redis协议(RESP)
    • 1. 简介
    • 2. 协议设计
      • 附加类型
    • 3. 数据传输
      • 请求和响应之间的交互模式
      • 客户端与服务端交互
    • 4. java实现 RESP协议
  • 3. 总结
  • 4.参考资料

在这里插入图片描述

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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/987087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

yapi以及gitlab的容器化部署

yapi部署: https://blog.csdn.net/Chimengmeng/article/details/132074922 gitlab部署 使用docker-compose.yml version: 3 services: web: image: twang2218/gitlab-ce-zh:10.5 restart: always hostname: 192.168.xx.xx environm…

Pythonの类

Python是一种面向对象编程语言,因此类在Python中是很重要的概念。类是一种定义数据和行为的模板,可以创建对象并针对特定的问题对其进行操作。 在Python中,类的定义以关键字"class"开头,后跟类的名称。类可以包含方法和…

NRF2401

NRF2401 简介工作模式 简介 NRF24L01 是 Nordic 公司的一款无线通信通信芯片,采用 FSK 调制,内部 集成自己的 Enhanced Short Burst 协议。可以实现点对点或是 1 对 6 的无线通信。 无线通信速度可以达到 2M(bps)。 工作模式 六…

ELK高级搜索(四)

文章目录 16.评分机制详解16.1 评分机制 TF\IDF16.2 Doc value16.3 query phase16.4 fetch phase16.5 搜索参数小总结 17.聚合入门17.1 聚合示例17.2 bucket和metric17.3 电视案例 18.java api实现聚合19.es7 sql新特性19.1 快速入…

【SpringCloud微服务--Eureka服务注册中心】

SpringCloud微服务全家桶学习笔记【持续更新】 gitee仓库 内容:SpringCloud SpringCloud alibaba 技术栈:Java8mavengit,githubNginxRabbitMQSpringBoot2.0 微服务架构概述 微服务架构是一种架构模式,它提倡将单一应用程序划…

sqlserver 各种集合、区间、 时间轴(持更)

1.所有有交集的区间 场景:在事件表里查找某年员工的岗位系数,并计算其加权平均数。case1:该员工是老员工,从2020年一直到2049年。case2:该员工是老员工,但是今年离职。case3:该员工是今年的新员…

亚马逊云科技与伊克罗德推出AI绘画解决方案——imAgine

在过去的数月中,亚马逊云科技已经推出了多篇介绍如何在亚马逊云科技上部署Stable Diffusion,或是如何结合Amazon SageMaker与Stable Diffusion进行模型训练和推理任务的内容。 为了帮助客户快速、安全地在亚马逊云科技上构建、部署和管理应用程序&#x…

OpenCV之形态学操作

形态学操作包含以下操作: 腐蚀 (Erosion)膨胀 (Dilation)开运算 (Opening)闭运算 (Closing)形态梯度 (Morphological Gradient)顶帽 (Top Hat)黑帽(Black Hat) 其中腐蚀和膨胀操作是最基本的操作,其他操作由这两个操作变换而来。 腐蚀 用一个结构元素…

谷歌Chrome庆祝15周年,推出全新设计!了解最新信息!

谷歌浏览器本月将满15岁,为了纪念这一时刻,它正在进行改造和升级。 这一点意义重大,因为Chrome在全球有数十亿人使用,因此谷歌所做的每一项改变都会对互联网以及这些人与互联网的互动方式产生巨大影响。即使你不使用Chrome或不关…

深入了解HTTP代理的工作原理

HTTP代理是一种常见的网络代理方式,它可以帮助用户隐藏自己的IP地址,保护个人隐私和安全。了解HTTP代理的工作原理对于使用HTTP代理的用户来说非常重要。本文将深入介绍HTTP代理的工作原理。 代理服务器的作用 HTTP代理的工作原理基于代理服务器的作用。…

一文讲透【静态脱敏实操】

1&#xff1a;直接上工具类 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version> </dependency>2:Hutool 支持的脱敏数据类型 现阶段最新版本的 Hutool 支持的脱敏数据类…

浅析目标检测入门算法:YOLOv1,SSD,YOLOv2,YOLOv3,CenterNet,EfficientDet,YOLOv4

本文致力于让读者对以下这些模型的创新点和设计思想有一个大体的认识&#xff0c;从而知晓YOLOv1到YOLOv4的发展源流和历史演进&#xff0c;进而对目标检测技术有更为宏观和深入的认知。本文讲解的模型包括&#xff1a;YOLOv1,SSD,YOLOv2,YOLOv3,CenterNet,EfficientDet,YOLOv4…

LaTeX总结-2023年9月8日

1. LaTeX总结 文章目录 1. LaTeX总结1.1. 定义作者&#xff0c;通讯作者&#xff0c;地址&#xff0c;宏包1.1.1. Example 11.1.2. Example 21.1.3. 特殊符号——作者标注注 1.2. 调整字体1.2.1. 数学模式下使用正体1.2.2. LaTeX内使用中文1.2.3. 正文文字 1.3. 常用符号及字母…

java - lua - redis 完成商品库存的删减

java调用lua脚本完成对商品库存的管理 主页链接 微风轻吟挽歌的主页 如若有帮助请帮忙点赞 //lua脚本 获取到内存不够的商品StringBuilder sb new StringBuilder();//定义一个数组存储可能缺少库存的值sb.append(" local table {} ");//获取值sb.append(" …

Java中的内部类

文章目录 &#x1f412;个人主页&#x1f3c5;JavaSE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;什么是内部类&#x1f415;内部类的分类&#x1f9f8;成员内部类&#x1f9f8;静态内部类&#x1f9f8;局部内部类&#x1f9f8;匿名内部类 &#x1f415;内部类的特点&a…

navicat设置mysql自动根据插入时间更新时间

使用navicat时间字段要素根据当前数据插入时间自动填充&#xff0c;可设置now()函数

CentOS 8 通过YUM方式升级最新内核

CentOS 8 通过YUM方式升级最新内核 查看当前内核 uname -r 4.18.0-193.6.3.el8_2.x86_64导入 ELRepo 仓库的公钥&#xff1a; rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org安装升级内核相关的yum源仓库(安装 ELRepo 仓库的 yum 源) yum install https://www…

栈和队列的概念及实现

文章目录 一、栈1.栈的概念2.数组作为顺序栈存储方式特点3.链栈特点4.代码实现栈(1).Stack.h(2).Stack.c(3).Test.c 二、队列2.区分顺序存储的队空和队满的三种处理方式3.代码实现(1).Quene.h(2).Quene.c 一、栈 1.栈的概念 栈的本质就是线性表&#xff0c;但它和队列一样&…

Nginx 学习(九)集群概述与LVS工作模式的配置

一 集群 1 概述 通过高速网络将很多服务器集中起来一起提供同一种服务&#xff0c;在客户端看来就像是只有一个服务器可以在付出较低成本的情况下获得在性能、可靠性、灵活性方面的相对较高的收益任务调度是集群系统中的核心技术 2 目的 提高性能。如计算密集型应用&…

记LGSVL Map Annotation使用

导入点云 内置的点云导入器工具提供了将最流行的点云文件格式&#xff08;PCD、PLY、LAS、LAZ&#xff09;转换为可用于仿真的数据所需的所有功能。 要访问点云导入器窗口&#xff0c;请在 Unity 编辑器中打开模拟器项目&#xff0c;然后导航到 Simulator/Import Point Cloud…