目录

一、前言

1.1 定义

1.2 数据类型

1.3 特性

二、RESP通信协议

2.1 引入Jedis的pom依赖

2.2 仿造Redis服务端

2.3 Jedis客户端发起请求

2.4 启动服务端和Jedis客户端

三、手写Redis客户端


一、前言

在此提前预热一下 Redis 的基本知识点。

Redis 全称 Remote Dictionary Server,意为远程字典服务。“字典”在 python 中的意义等同于 Java 中的键值对 map,所以可以理解为它的数据存储方式是 map。

1.1 定义

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

1.2 数据类型

值(value)可以是字符串(string),哈希(hash),列表(list),集合(sets)和有序集合(sorted sets)等类型。

1.3 特性

分布式、性能高(并发量10W)、支持发布订阅等。

二、RESP通信协议

Redis 服务端和客户端之间的应用层通信协议是 RESP(Redis Serialization Protocol),基于网络层 TCP 协议进行数据传输,然后根据解析规则解析相应的请求响应信息。

特点】:容易实现、解析快、人类可读。

接下来我们通过使用 Jedis 作为客户端,另外仿造一个 Redis 服务端的方式,尝试分析出 RESP 协议的规则。

2.1 引入Jedis的pom依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

2.2 仿造Redis服务端

/**
 * 假冒的Redis服务端,用来接收Jedis客户端发来的请求
 */
public class FakeRedisServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(6378);
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            byte[] request = new byte[1024];
            while (inputStream.read(request) != -1) {
                System.out.println(new String(request));
            }
            socket.close();
        }
    }
}

2.3 Jedis客户端发起请求

@Test
public void testFakeRedisServer() {
    Jedis jedis = new Jedis("localhost", 6378);
    jedis.set("name", "szh");
    /*String name = jedis.get("name");
    System.out.println(name);*/
    jedis.close();
}

2.4 启动服务端和Jedis客户端

下面是“set name szh”命令在 RESP 协议的体现(注意不要忽略每一段后的回车换行符 \r\n)。

*3
$3
SET
$4
name
$3
szh

经过简单分析:

  • “*3”:命令的内容有几段(“set name szh”为3,“get name”为2)
  • “$3”:大字符串类型的长度(“SET”为3,“name”为4,“szh”为3)

三、手写Redis客户端

经过上面的分析,看出来 RESP 协议确实比较易读,而且也比较容易实现。所以最后手写一个 Redis 客户端试试。

ps:这里我们只实现一下最常用的 get 和 set 操作。

实现方案也很简单,无非就是启动 socket,连接到真实的 Redis 服务,然后拼接各种客户端操作对应的 RESP 协议串,并将其发送和接收响应。

/**
 * 通过RESP协议可以自己实现Redis客户端
 */
@Slf4j
public class MyRedisClient {
    private Socket socket;
    private OutputStream outputStream;
    private InputStream inputStream;
    public MyRedisClient(String host, int port) throws IOException {
        this.socket = new Socket(host, port);
        this.outputStream = socket.getOutputStream();
        this.inputStream = socket.getInputStream();
    }

    public static void main(String[] args) throws IOException {
        MyRedisClient myRedisClient = new MyRedisClient("10.1.4.17", 6379);
        myRedisClient.set("age", "2000");
        String age = myRedisClient.get("age");
        log.info("age = {}", age);
    }

    public void set(String key, String value) throws IOException {
        StringBuffer command = new StringBuffer();
        command.append("*3").append("\r\n");
        command.append("$3").append("\r\n");
        command.append("SET").append("\r\n");
        command.append("$" + key.getBytes().length).append("\r\n");
        command.append(key).append("\r\n");
        command.append("$" + value.getBytes().length).append("\r\n");
        command.append(value).append("\r\n");
        log.info("set command: {}", command.toString());
        outputStream.write(command.toString().getBytes());
        // 在write之后,inputstream中就会有Redis服务端的返回replyMessage,需要在此消费掉
        byte[] response = new byte[1024];
        inputStream.read(response); // +OK
    }

    public String get(String key) throws IOException {
        StringBuffer command = new StringBuffer();
        command.append("*2").append("\r\n");
        command.append("$3").append("\r\n");
        command.append("GET").append("\r\n");
        command.append("$" + key.getBytes().length).append("\r\n");
        command.append(key).append("\r\n");
        log.info("get command: {}", command.toString());
        outputStream.write(command.toString().getBytes());
        byte[] response = new byte[1024];
        inputStream.read(response);
        return new String(response);
    }
}

以上。