redis序列化协议RESP

Posted 5ycode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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));

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

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获取第一时间的更新哦;

以上是关于redis序列化协议RESP的主要内容,如果未能解决你的问题,请参考以下文章

RedisRedis序列化协议(RESP)

redis序列化协议RESP

redis序列化协议RESP

redis序列化协议RESP

resp协议

Redis协议规范(译文)