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源码阅读四-我把redis6里的io多线程执行流程梳理明白了
redis源码阅读五-为什么大量过期key会阻塞redis?
本文是Redis源码剖析系列博文,有想深入学习Redis的同学,欢迎star和关注; Redis中文注解版:https://github.com/yxkong/redis/tree/5.0 如果觉得本文对你有用,欢迎一键三连; 同时可以关注微信公众号5ycode获取第一时间的更新哦;
以上是关于redis序列化协议RESP的主要内容,如果未能解决你的问题,请参考以下文章