• redis序列化协议RESP


    在阅读redis的源码的时候,一直忽略了一个问题,redis的通信协议,看流程的时候,有很多操作,比如:

                selectcmd = createObject(OBJ_STRING,
                    sdscatprintf(sdsempty(),
                    "*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
                    dictid_len, llstr));
    
    • 1
    • 2
    • 3
    • 4

    然后查了下官方,Redis 客户端使用一种称为RESP(Redis Serialization Protocal))的协议与 Redis 服务器进行通信。虽然该协议是专门为 Redis 设计的,但它也可以用于其他客户端-服务器软件项目。

    官方介绍,有三个特点:

    • 实现简单
    • 快速解析
    • 人类可读

    按官方的说明:

    • RESP 是一个序列化协议,支持简单字符串、错误、整数、批量字符串(Bulk Strings)和数组
    • RESP中,第一个字节决定数据类型;
    • 客户端和server端通过tcp或流来进行通信
    • 为了防止粘包 ,协议以\r\n(CRLF)结尾
    • 二进制安全

    官方举了一些例子

    • 简单字符串: 第一个字节为+,示例:+OK\r\n
    • 错误:第一个字节为-,示例:-Error message\r\n
    • 整数:第一个字节为:,示例::0\r\n
    • Bulk Strings(二进制安全的字符串):第一个字节为:$,示例:$5\r\nhello\r\n null 示例:$-1\r\n
      • $字节后紧跟着5是字节长度,以CRLF终止
      • 紧接着就是实际的字符hello,以CRLF终止
    • 数组:第一个字节:*,示例:*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n 空数组示例:*-1\r\n
      • * 字节后紧跟着2是数组的长度以\r\n结尾
      • $5 表示 表示下一个字符hello的长度,这个字符串以\r\n结尾
      • 在数组中$-1\r\n表示null元素

    我们来看一个具体的示例:

    set 5ycode yxk
    
    • 1

    image-20220902182211213

    我们来看下server端接收到了什么

    image-20220902181824195

    看下具体内容

    querybuf = "*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n*3\r\n$3\r\nset\r\n$6\r\n5ycode\r\n$3\r\nyxk\r\n"
    
    • 1

    对于server来说,接收到了两条命令

    • 一条是select 0
    • 一条是set 5ycode yxk

    看下内存里的内容

    image-20220902182029667

    image-20220902182051264

    和官方说明上对上了。

    我们看下java是如何封装的,就拿一个最简单的操作命令

    redisTemplate.opsForValue().set(key, value);
    
    • 1

    我们直接追set方法

    public interface ValueOperations<K, V> {
    
    	/**
    	 * Set {@code value} for {@code key}.
    	 *
    	 * @param key must not be {@literal null}.
    	 * @param value must not be {@literal null}.
    	 * @see Redis Documentation: SET
    	 */
    	void set(K key, V value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220902182450603

    class DefaultValueOperations<K, V> extends AbstractOperations<K, V> implements ValueOperations<K, V> {
    	@Override
    	public void set(K key, V value) {
    
    		byte[] rawValue = rawValue(value);
    		execute(new ValueDeserializingRedisCallback(key) {
    
    			@Override
    			protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
                    //最终是这里进行了调用
    				connection.set(rawKey, rawValue);
    				return null;
    			}
    		}, true);
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    继续翻代码

    public interface RedisStringCommands {
    	@Nullable
    	Boolean set(byte[] key, byte[] value);
    }
    
    • 1
    • 2
    • 3
    • 4

    因为我们操作的是string,我们直接看

    image-20220902182729874

    package org.springframework.data.redis.connection.jedis;
    
    class JedisStringCommands implements RedisStringCommands {
        public Boolean set(byte[] key, byte[] value) {
            Assert.notNull(key, "Key must not be null!");
            Assert.notNull(value, "Value must not be null!");
    
            try {
                if (this.isPipelined()) {
                    this.pipeline(this.connection.newJedisResult(this.connection.getRequiredPipeline().set(key, value), Converters.stringToBooleanConverter()));
                    return null;
                } else if (this.isQueueing()) {
                    this.transaction(this.connection.newJedisResult(this.connection.getRequiredTransaction().set(key, value), Converters.stringToBooleanConverter()));
                    return null;
                } else {
                    return Converters.stringToBoolean(this.connection.getJedis().set(key, value));
                }
            } catch (Exception var4) {
                throw this.convertJedisAccessException(var4);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们就是一次普通的调用,直接看 return Converters.stringToBoolean(this.connection.getJedis().set(key, value));我们直接看Jedis

    package redis.clients.jedis;
    
    public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
        AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands,
        ModuleCommands {
      @Override
      public String set(final String key, final String value) {
        checkIsInMultiOrPipeline();
        client.set(key, value);
        return client.getStatusCodeReply();
      }        
            
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    最终我们追到

    package redis.clients.jedis;
    
    public class Connection implements Closeable {
    public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {
        try {
          connect();
          //协议处理在这里
          Protocol.sendCommand(outputStream, cmd, args);
        } catch (JedisConnectionException ex) {
          try {
            String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
            if (errorMessage != null && errorMessage.length() > 0) {
              ex = new JedisConnectionException(errorMessage, ex.getCause());
            }
          } catch (Exception e) {
          }
          // Any other exceptions related to connection?
          broken = true;
          throw ex;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们看下最终的协议处理,我把常量转成具体的值看更直观些

    public final class Protocol {
     public static void sendCommand(final RedisOutputStream os, final ProtocolCommand command,
          final byte[]... args) {
        sendCommand(os, command.getRaw(), args);
      }
    
      private static void sendCommand(final RedisOutputStream os, final byte[] command,
          final byte[]... args) {
        try { 
          //以*号开头  
          os.write("*");
          //长度\r\n  
          os.writeIntCrLf(args.length + 1);
          os.write("$");
          //写了命令长度以后,又写了\r\n  
          os.writeIntCrLf(command.length);
          //写具体命令
          os.write(command);
          //写\r\n
          os.writeCrLf();
    
          for (final byte[] arg : args) {
            os.write("$");
            //写了参数长度以后,又写了\r\n  
            os.writeIntCrLf(arg.length);
            //具体值
            os.write(arg);
            //最后的\r\n  
            os.writeCrLf();
          }
        } catch (IOException e) {
          throw new JedisConnectionException(e);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    最终也转换成了*3\r\n$3\r\nset\r\n$6\r\n5ycode\r\n$3\r\nyxk\r\n

    redis系列文章

    redis源码阅读-入门篇

    redis源码阅读二-终于把redis的启动流程搞明白了

    redis源码阅读三-终于把主线任务执行搞明白了

    redis源码阅读四-我把redis6里的io多线程执行流程梳理明白了

    redis源码阅读五-为什么大量过期key会阻塞redis?

    redis源码六-redis中的缓存淘汰策略处理分析

    redis源码阅读-之哨兵流程

    redis源码阅读-持久化之RDB

    redis源码阅读-持久化之aof

    redis源码阅读-rehash详解

    redis源码阅读-发布与订阅pub/sub

    redis源码阅读-zset

    阅读redis源码的时候一些c知识

    阅读redis持久化RDB源码的时候一些c知识

    linux中的文件描述符与套接字socket

    redis中的IO多路复用select和epoll

    Reactor模式详解及redis如何使用

    redis的key过期了还能取出来?

    本文是Redis源码剖析系列博文,有想深入学习Redis的同学,欢迎star和关注; Redis中文注解版:https://github.com/yxkong/redis/tree/5.0 如果觉得本文对你有用,欢迎一键三连; 同时可以关注微信公众号5ycode获取第一时间的更新哦;

  • 相关阅读:
    【springBoot】资源文件的变量替换
    闭包、闭包应用场景
    Java 完全自学手册,从外包到大厂,再到年薪 100 万技术大佬都靠它
    TVS瞬态抑制二极管的工作原理和特点?|深圳比创达电子EMC
    大数据开发-Hadoop伪集群搭建
    【web前端特效源码】使用HTML5+CSS3+JavaScript制作一个响应式网站登陆页面|使用全屏可拖动图像滑块~手把手一步一步教学 ~快来收藏吧!
    sequence如何接收来自driver的transaction
    01_什么是深度学习
    【ASE+python学习】-批量识别石墨烯团簇结构中的吡啶氮,并删除与其相连的氢
    spring-data-mongodb生成的Query语句order字段顺序错误
  • 原文地址:https://blog.csdn.net/f80407515/article/details/126668843