Redis通信协议

文章目录

  • Redis通信协议
    • RESP协议
      • 数据类型
    • 模拟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:

    Redis通信协议_第1张图片

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

    Redis通信协议_第2张图片


模拟Redis客户端

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

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)

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

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

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

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

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

测试结果为:

Redis通信协议_第3张图片

你可能感兴趣的:(redis,后端学习,redis,数据库,缓存)