RESP协议知识点

本文默认你是使用过redis并了解redis的基础概念,学习redis入门并不难,给你介绍各种API使用也没啥意思。在这里 不会给你堆各种专业词汇,只有大白话

什么是协议? 就是一种格式规范,使用者们都要遵循这种规范已达到数据的可传递性和通用性,比如在7层网络模型中应用层的https协议,全世界的计算机都遵循它才有了互联网的快速发展,
       现在基本每个架构在使用公用协议的基础上都会有自己的小协议,这么想吧 只要涉及到数据传递的都会使用协议。
我们的redis当然也有自己的小本本了。就是RESP协议(REdis Serialization Protocol),这个协议简单说就是字符串。工作中大家都处理过日志吧,在生成日志时是不是我们会自定义一些格式已方便我们快速的定位问题,RESP 就可以理解成是一定规则(就当是分隔符吧)拼装起来的字符串

      我们知道redis的QPS号称10W+,使用内存操作数据是最快的原因,试想一下 如果采用了一种非常复杂的协议作为redis的数据载体,客户端吧数据装入,网络中传输,服务端在解析都是一种耗时的操作,
作者在设计上已经考虑到这种原因,所以设计了RESP协议,他最大的优势就是简单,容易实现,解析快,人类可读,
注意:RESP 虽然是为 Redis 设计的,但是同样也可以用于其他C/S架构,再次强调 重要的是 思想,思想 ,不要呗技术局限了

   一。协议说明

 关于RESP的说明,中文文档http://redisdoc.com的 功能文档-> 通信协议 里已经说的很明白了

引入文档说明如下

  • 状态回复(status reply)的第一个字节是 "+" (这几个可以理解为字符串的分隔符)

  • 错误回复(error reply)的第一个字节是 "-"

  • 整数回复(integer reply)的第一个字节是 ":"

  • 批量回复(bulk reply)的第一个字节是 "$"

  • 多条批量回复(multi bulk reply)的第一个字节是 "*"  (数组)

      新版统一请求协议在 Redis 1.2 版本中引入, 并最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式。

你的 Redis 客户端应该按照这个新版协议来进行实现。

在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。

以下是这个协议的一般形式:

*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF

举个例子, 以下是一个命令协议的打印版本:

*3   表示后面有三个命令字符 (set  mykey  myvalue这三个)/(也可以理解后面会出现三个 $)
$3  表示后面的字符串长度是3
SET
$5  表示后面的字符串长度是5
mykey
$7  表示后面的字符串长度是7
myvalue

看上面的示例 为什么他们是竖着的,答案很简单每一行的后面都会跟着一个结束的分隔符就是 \r\n

这个命令的实际协议值如下:

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

 二。手写redis客户端 

协议介绍清楚了,这就根据这协议实现我们自己的redis客户端吧,

第一步要解决的问题,怎么连接到redis,  如果你在你的IDEA中点进去看过就知道redis其实就是使用Socket连接到服务器的,

(不信可以比如jedis.set()点进去看看呦)

第二步要解决的问题,根据命令拼装RESP协议

第三步就是编码了,需求都理清楚了,编码工作就是小意思了

package com.abc.demo;

import org.springframework.boot.autoconfigure.data.redis.RedisProperties;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*****
 * 手写Redis客户端,理解 RESP 协议
 *
 * 例如 set mads shuaige
 * 转义成RESP的写法后
 * *3
 * $3
 * set
 * $4
 * mads
 * $7
 * shuaige
 *
 * ps:注意每个回车代表了结束符  \r\n
 * @author mads
 */
public class JedisDemo {
    private Socket jedis = null;

    private String CTRL = "\r\n";//结束语
    private String FUNCTION_XING = "*";//数组
    private String FUNCTION_DAOLE = "$";//字符 Bulk Strings 用于表示长度最大为512MB的单个二进制安全字符串。
    private String FUNCTION_NUM = ":";//数字
    private String FUNCTION_ERROR = "-";//错误回复
    private String FUNCTION_STATUS = "+";//状态回复

    private String API_SET = "SET";
    private String API_GET = "GET";
    private String API_INCRBY = "INCRBY";//


    public static void main(String[] args) throws IOException {
        JedisDemo jedis = new JedisDemo();
        jedis.inintJedis();
//        jedis.setString("mads", "googboy");
//        jedis.getString("mads");
        jedis.incrby("madsnum","1");
    }

    public void inintJedis() {
        try {
            jedis = new Socket("localhost", 6379);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*******
     * 模拟 Redis 的GET 命令
     * @param key
     */
    public void incrby(String key,String inc) throws IOException {

        //这里使用了 阿里开发规范里的提到的  try-with-resource的流处理方式,优雅的关闭流
        try (OutputStream out = jedis.getOutputStream()) {
            sendCommand(out, API_INCRBY, key.getBytes(),inc.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /*******
     * 模拟 Redis 的GET 命令
     * @param key
     */
    public void getString(String key) throws IOException {

        //这里使用了 阿里开发规范里的提到的  try-with-resource的流处理方式,优雅的关闭流
        try (OutputStream out = jedis.getOutputStream()) {
            sendCommand(out, API_GET, key.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /*******
     * 模拟 Redis 的SET 命令
     * @param key
     * @param value
     */
    public void setString(String key, String value) throws IOException {

        //这里使用了 阿里开发规范里的提到的  try-with-resource的流处理方式,优雅的关闭流
        try (OutputStream out = jedis.getOutputStream()) {
            sendCommand(out, API_SET, key.getBytes(), value.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    /******
     *  转义成resp 协议 并执行
     * @param out
     * @param command redis 的api set,hget...  ,这里可以优化成枚举
     * @param args
     * @throws IOException
     */
    private void sendCommand(OutputStream out, String command, byte[]... args) throws IOException {
        //比如  key ==mads   value== shuaige

        //在确定的字符串个数的情况下传入大小。避免了后续的扩容操作,提升效率
        StringBuilder ss = new StringBuilder(20);

        // *3
        ss.append(FUNCTION_XING).append(args.length + 1).append(CTRL);
        //$3
        ss.append(FUNCTION_DAOLE).append(command.length()).append(CTRL);
        //SET
        ss.append(command).append(CTRL);

        //$4mads$7shuaige
        for (byte[] arg : args) {
            ss.append(FUNCTION_DAOLE).append(arg.length).append(CTRL);

            ss.append(new String(arg)).append(CTRL);
        }
        System.out.println("自己客户端拼装成的RESP协议串->:" + ss);
        out.write(ss.toString().getBytes());
        out.flush();

        try (InputStream inputStream = jedis.getInputStream()) {

            byte[] buff = new byte[1024];
            int len = inputStream.read(buff);
            if (len > 0) {
                String msg = new String(buff, 0, len);

                System.out.println("自己客户端收到Redis服务端返回的数据:" + msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章