RESP协议

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

客户端(client)向服务端(server)发送一条命令

服务端解析并执行命令,返回响应结果给客户端

因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。

而在Redis中采用的是RESP(Redis Serialization Protocol)协议:

  • Redis 1.2版本引入了RESP协议
  • Redis 2.0版本中成为与Redis服务端通信的标准,称为RESP2
  • Redis 6.0版本中,从RESP2升级到了RESP3协议,增加了更多数据类型并且支持6.0的新特性--客户端缓存

但目前,默认使用的依然是RESP2协议,也是我们要学习的协议版本(以下简称RESP)。

数据类型

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

  • 单行字符串:首字节是 ‘+’ ,后面跟上单行字符串,以CRLF( "\r\n" )结尾。例如返回"OK": "+OK\r\n"
  • 错误(Errors):首字节是 ‘-’ ,与单行字符串格式一样,只是字符串是异常信息,例如:"-Error message\r\n"
  • 数值:首字节是 ‘:’ ,后面跟上数字格式的字符串,以CRLF结尾。例如:":10\r\n"
  • 多行字符串:首字节是 ‘$’ ,表示二进制安全的字符串,最大支持512MB:
    • 除0、-1外的正数表示字符串的长度,长度部分以\r\n结束($5\r\nhello\r\n)
    • 如果大小为0,则代表空字符串:"$0\r\n\r\n"
    • 如果大小为-1,则代表不存在:"$-1\r\n"
  • 数组:首字节是 ‘*’,后面跟上数组元素个数,再跟上元素,元素数据类型不限:
1653982993020

基于Socket实现自定义Redis客户端

Redis支持TCP通信,因此我们可以使用Socket来模拟客户端,与Redis服务端建立连接:

 public class Main {
 ​
     static Socket s;
     static PrintWriter writer;
     static BufferedReader reader;
 ​
     public static void main(String[] args) {
         try {
             // 1.建立连接
             String host = "192.168.150.101";
             int port = 6379;
             s = new Socket(host, port);
             // 2.获取输出流、输入流
             writer = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8));
             reader = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));
 ​
             // 3.发出请求
             // 3.1.获取授权 auth 123321
             sendRequest("auth", "123321");
             Object obj = handleResponse();
             System.out.println("obj = " + obj);
 ​
             // 3.2.set name 虎哥
             sendRequest("set", "name", "虎哥");
             // 4.解析响应
             obj = handleResponse();
             System.out.println("obj = " + obj);
 ​
             // 3.2.set name 虎哥
             sendRequest("get", "name");
             // 4.解析响应
             obj = handleResponse();
             System.out.println("obj = " + obj);
 ​
             // 3.2.set name 虎哥
             sendRequest("mget", "name", "num", "msg");
             // 4.解析响应
             obj = handleResponse();
             System.out.println("obj = " + obj);
        } catch (IOException e) {
             e.printStackTrace();
        } finally {
             // 5.释放连接
             try {
                 if (reader != null) reader.close();
                 if (writer != null) writer.close();
                 if (s != null) s.close();
            } catch (IOException e) {
                 e.printStackTrace();
            }
        }
    }
 ​
     private static Object handleResponse() throws IOException {
         // 读取首字节
         int prefix = reader.read();
         // 判断数据类型标示
         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;
                }
                 if (len == 0) {
                     return "";
                }
                 // 再读数据,读len个字节。我们假设没有特殊字符,所以读一行(简化)
                 return reader.readLine();
             case '*':
                 return readBulkString();
             default:
                 throw new RuntimeException("错误的数据格式!");
        }
    }
 ​
     private static Object readBulkString() throws IOException {
         // 获取数组大小
         int len = Integer.parseInt(reader.readLine());
         if (len <= 0) {
             return null;
        }
         // 定义集合,接收多个元素
         List<Object> list = new ArrayList<>(len);
         // 遍历,依次读取每个元素
         for (int i = 0; i < len; i++) {
             list.add(handleResponse());
        }
         return list;
    }
 ​
     // set name 虎哥
     private static void sendRequest(String ... args) {
         writer.println("*" + args.length);
         for (String arg : args) {
             writer.println("$" + arg.getBytes(StandardCharsets.UTF_8).length);
             writer.println(arg);
        }
         writer.flush();
    }
 }
此作者没有提供个人介绍
最后更新于 2024-08-14