在阅读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 设计的,但它也可以用于其他客户端-服务器软件项目。
官方介绍,有三个特点:
按官方的说明:
粘包
,协议以\r\n(CRLF)
结尾官方举了一些例子
+
,示例:+OK\r\n
-
,示例:-Error message\r\n
:
,示例::0\r\n
$
,示例:$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 Redis Documentation: SET
*/
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获取第一时间的更新哦;