ITPub博客

首页 > 数据库 > 数据库开发技术 > redis序列化协议RESP

redis序列化协议RESP

原创 数据库开发技术 作者:yxkong 时间:2022-09-02 18:44:23 0 删除 编辑

在阅读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));

然后查了下官方,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

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

看下具体内容

 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"

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

  • 一条是select 0

  • 一条是set 5ycode yxk

看下内存里的内容


和官方说明上对上了。

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

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

我们直接追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 <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
  */
  void set(K key, V value);
 }

 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);
 }
 
 }

继续翻代码

 public interface RedisStringCommands {
 @Nullable
 Boolean set(byte[] key, byte[] value);
 }

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


 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);
        }
    }
 }

我们就是一次普通的调用,直接看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();
  }        
         
 }

最终我们追到

 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;
    }
  }
 }

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

 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);
    }
  }
 }

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



原文作者:5ycode;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-03-26

  • 博文量
    124
  • 访问量
    71503