Redis笔记(五)- 手写Redis

本文介绍了Redis的RESP协议,强调其简单、快速和可读性特点。通过实例展示了如何使用Java模拟客户端和服务器进行交互,理解RESP协议的结构。此外,还实现了简易版的Jedis和Pipeline,探讨了Pipeline在批量操作中的效率优势。
摘要由CSDN通过智能技术生成

Redis的通信协议-RESP

Redis的客户端使用的是RESP协议与Redis的服务器进行通信交互,这个协议不仅仅在Redis中使用,我们也可以用于其他的C-S软件项目,RESP主要的特点如下:①实现简单②解析快速③人类可读

接下来,我们围绕着RESP协议写几段代码来讲解它,这样应该能更深刻的理解RESP协议。

一、RESP协议传输的数据内容(结构)是怎样的?

RESP 底层采用的是 TCP 的连接方式,我们只需要使用Jedis作为客户端,自己模拟一个Socke作为服务端来接收Jedis发送过来的内容,就可以看到Jedis发送的内容是什么了。(如果没有网络编程基础知识的读者可以看我的网络编程与Netty专栏,有详细的学习讲解)

RedisClient

public class RedisClient {
    public static void main(String[] args) {
        //redis服务端地址和端口需要换成自己的
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.set("test-protocol", "hello world!"); //set "test-protocol" "hello world!"
    }
}

RedisServer

public class RedisServer {
    public static void main(String[] args)  {
        try {
            ServerSocket serverSocket = new ServerSocket(6379);
            Socket socket = serverSocket.accept();
            byte[] arr = new byte[64];
            socket.getInputStream().read(arr);
            System.out.println(new String(arr));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

运行结果:

在这里插入图片描述

把协议内容拿出来详细讲讲都代表什么意思
*3   //*<参数数量>/r/n
$3   //$<参数 1 的字节长度>
SET	 //<参数1的具体数据>
$13	 //$<参数 2 的字节长度>
test-protocol  //<参数2的具体数据>
$12  //$<参数 3 的字节长度>
hello world!  //<参数3的具体数据>

二、自己手写一个简易Jedis

看完上面第一段代码,不知道是否各位读者是否想要自己手写一个Jedis作为客户端向服务端发送请求呢?下面笔者写一个简易的Jedis小案例,同时也是为了验证上面RESP协议的结构是否跟我们理解的一样。

MyJedis

public class MyJedis {
    public static String auth(Socket socket,String password) throws IOException {
        StringBuffer str = new StringBuffer();
        str.append("*2").append("\r\n");
        str.append("$4").append("\r\n");
        str.append("auth").append("\r\n");
        str.append("$").append(password.getBytes().length).append("\r\n");
        str.append(password).append("\r\n");
        socket.getOutputStream().write(str.toString().getBytes());
        byte[] response = new byte[2048];
        socket.getInputStream().read(response);
        return new String(response);
    }

    public static String set(Socket socket,String key, String value) throws IOException {
        StringBuffer str = new StringBuffer();
        str.append("*3").append("\r\n");
        str.append("$3").append("\r\n");
        str.append("SET").append("\r\n");
        str.append("$").append(key.getBytes().length).append("\r\n");
        str.append(key).append("\r\n");
        str.append("$").append(value.getBytes().length).append("\r\n");
        str.append(value).append("\r\n");
        socket.getOutputStream().write(str.toString().getBytes());
        byte[] response = new byte[2048];
        socket.getInputStream().read(response);
        return new String(response);

    }
    public static String get(Socket socket,String key) throws IOException {
        StringBuffer str = new StringBuffer();
        str.append("*2").append("\r\n");
        str.append("$3").append("\r\n");
        str.append("GET").append("\r\n");
        str.append("$").append(key.getBytes().length).append("\r\n");
        str.append(key).append("\r\n");
        socket.getOutputStream().write(str.toString().getBytes());
        byte[] response = new byte[2048];
        socket.getInputStream().read(response);
        return new String(response);
    }
    public static void main(String[] args) throws IOException {
        //需要换成自己的ip.端口.密码
        Socket socket = new Socket("127.0.0.1",6379);
        auth(socket,"123456");
        set(socket,"MyJedis","good");
        System.out.println(get(socket,"MyJedis"));
    }
}

运行结果:

在这里插入图片描述

图中可以看出,确实是将MyJedis-good这个k-v对存入了Redis,并且Redis的服务器返回的数据也是使用RESP协议

三、使用pipeline批量删除,批量执行命令,并且对比不使用pipeline进行删除操作所需要花费的时间

UsePipeline

public class UsePipeline {
    //删除10000个key
    public static int arraylength = 10000;
    public static String[] keys = new String[arraylength/2];
    public static void initRedisData() {
        //换成自己的ip 端口 密码
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.auth("123456");
        String[] str = new String[arraylength];

        int j = 0;
        for(int i = 0 ; i < str.length/2; i ++ ){
            str[j] = "key:"+i;
            str[j+1] = "v"+i;
            keys[i]= str[j];
            j = j+2;
        }
        jedis.mset(str);
        jedis.close();
    }
    
    public static void main(String[] args) {
        initRedisData();
        long t = System.currentTimeMillis();
        //不使用pipeline
        delNoPipe(keys);
        //使用pipeline    
	    //delByPipe(keys);
        System.out.println(System.currentTimeMillis()-t);
    }

    public static void delNoPipe(String...keys){
        //换成自己的ip 端口 密码
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.auth("123456");
        for(String key:keys){
            jedis.del(key);
        }
        jedis.close();
    }
    public static void delByPipe(String...keys){
        //换成自己的ip 端口 密码
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.auth("123456");
        Pipeline pipelined = jedis.pipelined();
        for(String key:keys){
            pipelined.del(key);
        }
        pipelined.sync();
        jedis.close();
    }
}

测试结果:

在这里插入图片描述

四、看完第三段代码,是否有想过自己手写一个简易的Pipeline?那就开始吧。

MyPipeline

public class MyPipeline {
    private Socket socket;
    public MyPipeline(Socket socket) {
        this.socket = socket;
    }
    /**
     * 传入数组KEY,批量删除
     * String[]{"key:0","key:1","key:2","key:3","key:4"})
     */
    public void mdel(String... keys) throws Exception {
        StringBuffer str = new StringBuffer();
        for(String key:keys){
            str.append("*2").append("\r\n");
            str.append("$3").append("\r\n");
            str.append("del").append("\r\n");
            str.append("$").append(key.getBytes().length).append("\r\n");
            str.append(key).append("\r\n");
        }
        socket.getOutputStream().write(str.toString().getBytes());
    }

    public  String auth(String password) throws IOException {
        StringBuffer str = new StringBuffer();
        str.append("*2").append("\r\n");
        str.append("$4").append("\r\n");
        str.append("auth").append("\r\n");
        str.append("$").append(password.getBytes().length).append("\r\n");
        str.append(password).append("\r\n");
        socket.getOutputStream().write(str.toString().getBytes());
        byte[] response = new byte[2048];
        socket.getInputStream().read(response);
        return new String(response);
    }
}
public class UsePipeline {
    public static int arraylength = 10000;
    public static String[] keys = new String[arraylength/2];


    public static void initRedisData() {
        Jedis jedis = new Jedis(RedisUtils.ip, RedisUtils.port);
        jedis.auth("12345678");
        String[] str = new String[arraylength];

        int j = 0;
        for(int i = 0 ; i < str.length/2; i ++ ){
            str[j] = "key:"+i;
            str[j+1] = "v"+i;
            keys[i]= str[j];
            j = j+2;
        }
        jedis.mset(str);
        jedis.close();
    }

    public static void main(String[] args) {
        initRedisData();
        long t = System.currentTimeMillis();
        delByMyPipe(keys);
        System.out.println(System.currentTimeMillis()-t);
    }

    public static void delByMyPipe(String...keys){
        try {
            //换成自己的ip 端口 密码
            Socket socket = new Socket("127.0.0.1",6379);
            MyPipeline myPipeline = new MyPipeline(socket);
            myPipeline.auth("123456");
            myPipeline.mdel(keys);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

在这里插入图片描述

可以看到,使用了pipeline的支持后,在批量删除时,可以大大的减少时间,它的原理也很简单,原本的删除操作一次请求只能删除一个key,而前面我们说过一次完整的redis请求主要包括网络传输和执行命令的过程,如果每删除一个key就需要发出一次网络请求,是比较浪费时间的,而pipeline则是将所有的删除命令打包在了一起,通过一次请求发送到服务器端进行删除,这样可以大大减少时间浪费。在案例三和四的结果中也很明显,删除同样的数据,消耗时间大大减少。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值