Redis通信协议

文章目录

Redis通信协议

RESP协议

Redis是一个CS架构的软件,通信一般分为两步(不包含pipeline和PubSub):

  1. 客户端(client)向服务端(server)发送一条命令。
  2. 服务器解析并执行命令,返回响应结果到客户端。

因此,客户端发送命令好服务端响应结果的格式需要有一个规范(否则便无法正常通信),这个规范便是通信协议。

在Redis中采用的是RESP协议:

  • Redis1.2版本引入RESP协议。
  • Redis2.0版本中称为Redis服务通信的标准,成为REST2。
  • Redis6.0版本中,从RESP2升级到RESP3协议,增加了更多数据类型并且支持6.0的新特性(客户端缓存)。

但目前默认使用的依旧是RESP2协议,

数据类型

在RESP中,通过首字节的字符来区分不同数据类型,常用的数据类型包括5种:

  • 单行字符串:首字节是 '+ ' ,后面跟上单行字符串,以CRLF( "\r\n" )结尾。例如返回"OK": "+OK\r\n"。

  • 错误(Errors):首字节是 '-' ,与单行字符串格式一样,只是字符串是异常信息,例如:"-Error message\r\n"。

  • 数值:首字节是 ':' ,后面跟上数字格式的字符串,以CRLF结尾。例如:":10\r\n"。

  • 多行字符串:首字节是 '$' ,表示二进制安全的字符串,最大支持512MB:

    • 如果大小为0,则代表空字符串:"$0\r\n\r\n"。
    • 如果大小为-1,则代表不存在:"$-1\r\n"
  • 数组:首字节是 '*****',后面跟上数组元素个数,再跟上元素,元素数据类型不限:


模拟Redis客户端

Redis支持TCP通信,这里边使用Socket模拟客户端与Redis建立连接

java 复制代码
public static void  RedisRequest(String address,String password,String...request) {
    Socket socket = null;
    PrintWriter writer = null;
    BufferedReader reader = null;
    try {
        // 1. 建立连接
        String host = address.substring(0,address.indexOf(":")); // redis的所在ip地址
        int port = Integer.parseInt(address.substring(address.indexOf(":") + 1)); //redis的端口号(默认6379)
        socket = new Socket(host, port);
        // 2. 获取输出流、输入流
        writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
        // 获取授权(登录)
        if(password!=null && !"".equals(password)){
            sendRequest(writer,"auth "+password);
        }
        // 3. 发出请求 set name xiaoming
        sendRequest(writer,request);
        // 4. 解析响应(多次解析)
        for(int i = 0;i<request.length;i++){
            Object obj = handleResponse(reader);
            System.out.println(obj);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 5. 释放连接
        if(reader !=null){
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(writer!=null){
            writer.close();
        }
        if(socket!=null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

发送一条set命令和一条get命令(set name xiaoming , get name)

java 复制代码
private static void sendRequest (PrintWriter writer,String...request) {
    String[][] req = new String[request.length][];
    for(int i = 0;i<request.length;i++){
        req[i] = request[i].split(" ");
    }
    for (String[] strings : req) {
        int n = strings.length;
        writer.println("*" + n);
        for (String s : strings) {
            writer.println("$" + s.getBytes(StandardCharsets.UTF_8).length);
            writer.println(s);
        }
        writer.flush();
    }
}

根据响应的类型来读取响应结果

java 复制代码
private static Object handleResponse (BufferedReader reader) {
    // 读取首字节
    int prefix  = 0;
    try {
        prefix = reader.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        // 判断数据类型标示
        switch (prefix) {
            case '+' -> {  // 单行字符串,直接读一行
                return reader.readLine();
            }
            case '-' -> // 异常,读一行
                throw new RuntimeException(reader.readLine());
            case ':' -> { // 数字
                return Long.parseLong(reader.readLine());
            }
            case '$' -> { // 多行字符串
                // 读长度
                int len = Integer.parseInt(reader.readLine());
                if (len == -1) {
                    return null;
                } else if (len == 0) {
                    return "";
                }
                //读数据(这里使用的是字符流,直接读一行)
                return reader.readLine();
            }
            case '*' -> {
                return readbulkString(reader);
            }
            default -> throw new RuntimeException("错误的数据格式!");
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

private static Object readbulkString (BufferedReader reader) {
    // 获取数据大小
    int len = 0;
    try {
        len = Integer.parseInt(reader.readLine());
    } catch (IOException e) {
        e.printStackTrace();
    }
    if(len <= 0){
        return null;
    }
    // 接收多个元素
    List<Object> list = new ArrayList<>(len);
    // 遍历,依次获取每个元素
    for(int i = 0;i < len;i++){
        list.add(handleResponse(reader));
    }
    return list;
}

使用单元测试方法,测试发送请求和接收响应

java 复制代码
@Test
public void test(){
    Main.RedisRequest("192.168.45.138:6379",null,"set name xiaoming","get name");
}

测试结果为:

相关推荐
wWYy.5 分钟前
Mysql:主键索引 唯一索引 普通索引 前缀索引
数据库·mysql
宝杰X727 分钟前
Android Room3 多平台数据库
android·数据库
sky_8106134 小时前
Oracle ERP 各模块业务管理功能及底层表说明
数据库·oracle
YMatrix 官方技术社区5 小时前
CittaBase vs. Neo4j :原生图性能实测与混合检索实践
数据库·功能测试·ymatrix
闲猫6 小时前
LangChain / Integrations / Integrations by component / Tool
java·数据库·langchain
xqqxqxxq6 小时前
SQL 连接查询技术笔记
数据库·笔记·sql
消失的旧时光-19437 小时前
第六篇:Redis Hash——一个用户对象应该保存成 JSON,还是保存成 Hash?
redis·json·哈希算法
Databend7 小时前
从万亿级大模型到全线应用:Databend Cloud 助力头部 AI 企业构建全链路 Trace 数据管道
大数据·数据库·sql
MC皮蛋侠客7 小时前
SQLAlchemy 系列(十一):从 1.x 到 2.x——渐进迁移与数据访问层治理
数据库·python