redis 指令:
redis.io/commands
源码地址:
https://github.com/xetorthio/jedis
maven依赖:
http://mvnrepository.com/artifact/redis.clients/jedis
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.8.1version>
dependency>
jedis底层主要有两个类:
redis.clients.jedis.Protocol
redis.clients.jedis.Connection
Connection负责client与server之间通信,Protocol是client与server之间通信协议。
public class Connection implements Closeable {
private static final byte[][] EMPTY_ARGS = new byte[0][];
private String host = "localhost"; //redis服务器地址(默认"localhost")
private int port = 6379;//port:服务端号(默认6379)
private Socket socket;
private RedisOutputStream outputStream;//redis-client发送给redis-server的内容
private RedisInputStream inputStream;//redis-server返回给redis-client的内容
private int pipelinedCommands = 0;//管道命令数
private int connectionTimeout = 2000;//连接超时时间(默认2000ms)
private int soTimeout = 2000;//响应超时时间(默认2000ms)
private boolean broken = false;
...
/**主要方法*/
//连接
public void connect() {
if(!this.isConnected()) {
try {
this.socket = new Socket();
this.socket.setReuseAddress(true);
this.socket.setKeepAlive(true);
this.socket.setTcpNoDelay(true);
this.socket.setSoLinger(true, 0);
this.socket.connect(new InetSocketAddress(this.host, this.port), this.connectionTimeout);
this.socket.setSoTimeout(this.soTimeout);
this.outputStream = new RedisOutputStream(this.socket.getOutputStream());
this.inputStream = new RedisInputStream(this.socket.getInputStream());
} catch (IOException var2) {
this.broken = true;
throw new JedisConnectionException(var2);
}
}
}
//发送命令内容
protected Connection sendCommand(Command cmd, byte[]... args) {
try {
this.connect();
Protocol.sendCommand(this.outputStream, cmd, args);
++this.pipelinedCommands;
return this;
} catch (JedisConnectionException var6) {
JedisConnectionException ex = var6;
try {
String errorMessage = Protocol.readErrorLineIfPossible(this.inputStream);
if(errorMessage != null && errorMessage.length() > 0) {
ex = new JedisConnectionException(errorMessage, ex.getCause());
}
} catch (Exception var5) {
;
}
this.broken = true;
throw ex;
}
}
}
//协议
public final class Protocol {
//命令的发送都是通过redis.clients.jedis.Protocol的sendCommand来完成的,就是对RedisOutputStream写入字节流
/**
*[*号][消息元素个数]\r\n ( 消息元素个数 = 参数个数 + 1个命令)
*[$号][命令字节个数]\r\n
*[命令内容]\r\n
*[$号][参数字节个数]\r\n
*[参数内容]\r\n
*[$号][参数字节个数]\r\n
*[参数内容]\r\n
*/
private static void sendCommand(RedisOutputStream os, byte[] command, byte[]... args) {
try {
os.write((byte)42);
os.writeIntCrLf(args.length + 1);
os.write((byte)36);
os.writeIntCrLf(command.length);
os.write(command);
os.writeCrLf();
byte[][] e = args;
int var4 = args.length;
for(int var5 = 0; var5 < var4; ++var5) {
byte[] arg = e[var5];
os.write((byte)36);
os.writeIntCrLf(arg.length);
os.write(arg);
os.writeCrLf();
}
} catch (IOException var7) {
throw new JedisConnectionException(var7);
}
}
}
//返回的数据是通过读取RedisInputStream 进行解析处理后得到的
/**
* public static final byte PLUS_BYTE = 43;
* public static final byte DOLLAR_BYTE = 36;
* public static final byte ASTERISK_BYTE = 42;
* public static final byte COLON_BYTE = 58;
* "+": 状态回复(status reply) PLUS_BYTE
* *
* 状态回复通常由那些不需要返回数据的命令返回,这种回复不能包含新行。
* eg:
* cli: set name zhangsan
* server: +OK
*
*
* "$": 批量回复(bulk reply) DOLLAR_BYTE
* 服务器使用批量回复来返回二进制安全的字符串,字符串的最大长度为 512 MB。
* eg:
* cli: get name
* server: $8\r\nzhangsan\r\n
* 空批量回复:
* 如果被请求的值不存在, 那么批量回复会将特殊值 -1 用作回复的长度值。当请求对象不存在时,客户端应该返回空对象,而不是空字符串。
*
* "*": 多条批量回复(multi bulk reply) ASTERISK_BYTE
* * 多条批量回复是由多个回复组成的数组, 数组中的每个元素都可以是任意类型的回复, 包括多条批量回复本身。
* eg:
* cli: lrange mylist 0 3
* server: *4\r\n
* :1\r\n
* :2\r\n
* :3\r\n
* $3\r\n
* foo\r\n
* 多条批量回复也可以是空白的,
* eg:
* cli: lrange mylist 7 8
* server: *0\r\n
* 无内容的多条批量回复(null multi bulk reply)也是存在的, 比如当 BLPOP 命令的阻塞时间超过最大时限时, 它就返回一个无内容的多条批量回复, 这个回复的计数值为 -1 :
* eg:
* cli: blpop key 1
* server: *-1\r\n
* 多条批量回复中的元素可以将自身的长度设置为 -1 , 从而表示该元素不存在, 并且也不是一个空白字符串(empty string)。
*
* ":": 整数回复(integer reply) COLON_BYTE
* * 整数回复就是一个以 ":" 开头, CRLF 结尾的字符串表示的整数。
* eg:
* cli: exists name
* server: :1
*
* "-": 错误回复(error reply) MINUS_BYTE
*/
private static Object process(RedisInputStream is) {
byte b = is.readByte();
if(b == 43) {
return processStatusCodeReply(is);
} else if(b == 36) {
return processBulkReply(is);
} else if(b == 42) {
return processMultiBulkReply(is);
} else if(b == 58) {
return processInteger(is);
} else if(b == 45) {
processError(is);
return null;
} else {
throw new JedisConnectionException("Unknown reply: " + (char)b);
}
}
}
以Jedis的get方法为例:
public String get(String key) {
this.checkIsInMultiOrPipeline();
this.client.sendCommand(Command.GET, new String[]{key});
return this.client.getBulkReply();
}
1:this.checkIsInMultiOrPipeline();
进行无事务检查 Jedis不能进行有事务的操作 带事务的连接要用redis.clients.jedis.Transaction类。
2:this.client.sendCommand(Command.GET, new String[]{key});
2.1:redis.clients.jedis.Connection connect()方法建立连接
2.2:public final class Protocol sendCommand()方法向RedisOutputStream写入命令
2.3:在命令写入成功之后,会将Connection的piplinedCommands 属性自增一,表示在管道中已经有一个命令了
3:return this.client.getBulkReply();
get方法使用getBulkReply()获取返回结果,其他见上文redis.clients.jedis.Protocol process()方法
redis是一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。
所以在多条命令需要处理时,使用pipeline效率会快得多。
通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。pipeline方式将client端命令一起发出,redis server会处理完多条命令后,将结果一起打包返回client,从而节省大量的网络延迟开销。需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试。
Pipeline pipeline = jedis.pipelined();
pipeline.set("a","a");
pipeline.get("a");
pipeline.set("b","b");
pipeline.get("b");
pipeline.del("a");
pipeline.get("a");
List
结果如下:
OK
a
OK
b
1
null
Redis Java Client Jedis 源码分析
Jedis 最简单的例子分析
深入Jedis
Jedis - Redis通信协议
Redis学习笔记7–Redis管道(pipeline)