Jedis源码分析
Jedis继承关系
Jedis提供了redis的客户端的连接和命令查询.从jedis继承关系中,Jedis实现很多的命令接口,每个接口都定义了不同的操作形式,这符合面向对象开发原则中的接口隔离原则和单一职责原则。下面的接口声明了相关的redis命令操作,每个接口都负责对一部分的命令进行方法声明。
下列接口由父类BinaryJedis所依赖的接口
- BasicCommands:提供基础的查询命令,如ping,quit,flushdb
- BinaryJedisCommands:提供了针对redis数据结构的CURD等操作,其中参数(K-V)必须以byte数组形式提供
- MultiKeyBinaryCommands:提供了针对redis数据结构的CURD等批量操作,其中参数(K-V)必须以byte数组形式提供
- AdvancedBinaryJedisCommands:提供高级操作redis的命令,如config相关,slowlog,client等命令,其中参数(K-V)必须以byte数组形式提供
- BinaryScriptingCommands:提供Lua脚本运行命令,命令必须以byte数组形式提供。
Jedis所依赖的接口
- JedisCommands:提供了针对redis数据结构的CURD等操作,其中参数(K-V)必须以String形式提供
- MultiKeyCommands:提供了针对redis数据结构的CURD等批量操作,其中参数(K-V)必须以String数组形式提供
- AdvancedJedisCommands:提供高级操作redis的命令,如config相关,slowlog,client等命令,其中参数(K-V)必须以String形式提供
- ScriptingCommands:提供Lua脚本运行命令,命令必须以String形式提供。
- BasicCommands:提供如ping,quit,flushDb运维类命令
- ClusterCommands:提供集群状态查看,集群操作的命令
- SentinelCommands:提供哨兵操作命令
- ModuleCommands:提供redis模块加载和卸载命令
除了方法接口声明之外,Jedis提供了客户端接口Client,使用该类仍然可以连接Redis服务端并进行相关的命令操作.Client本身只是提供相关的命令方法,而各方法的声明则需要Commands接口,连接操作则需要Connection类,也就是说Client类类似于一个前台,可以提供各种服务,而具体的实现则依赖于Connection和Commands.Client提供方法与jedis基本一致,少于不同的是,Client类不提供get()方法的返回值。
package com.zzz.jedis.service;
import org.junit.Test;
import redis.clients.jedis.Client;
public class ClientTest {
@Test
public void testClient() {
Client client = new Client("localhost");
client.connect();
System.out.println(client.isConnected());
client.set("a", "zhangsan");
client.close();
}
}
Jedis如何连接服务端
Jedis通过Socket对Redis 服务端进行连接,而Jedis和Client本身并没有socket连接方法的实现,相关的连接方法都在Connection类中,观察如下的继承关系,Connection类是其顶层的实现类。Jedis或者Client发送命令时,必须通过Connection类的connect()方法建立TCP连接。

在使用Connection类时,需提供相关的参数进行实例化,从构造方法可以看到,需要提供主机名,如果不设置,则使用默认名localhost.连接端口,不设置则使用默认端口6379,连接超时时长(如果不进行设置,则默认与socketTimeout保持一致),socket读取超时时长(如果不进行设置,则使用默认时长2000ms),SSL加密传输则是可选的。在连接时必须判断当前对象的连接状态,如代码2-1所示,如果符合条件则新建Socket实例。
代码1-1:Connection构造方法
public Connection(final String host, final int port, final boolean ssl,
SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier) {
this.host = host;
this.port = port;
this.ssl = ssl;
this.sslSocketFactory = sslSocketFactory;
this.sslParameters = sslParameters;
this.hostnameVerifier = hostnameVerifier;
}
代码2-1:socket是否连接状态
public boolean isConnected() {
return socket != null && socket.isBound() && !socket.isClosed() && socket.isConnected()
&& !socket.isInputShutdown() && !socket.isOutputShutdown();
}
代码2-2:socket连接
public void connect() {
if (!isConnected()) {
try {
socket = new Socket();
// ->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
//SSL 加密连接
if (ssl) {
if (null == sslSocketFactory) {
sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
}
socket = sslSocketFactory.createSocket(socket, host, port, true);
if (null != sslParameters) {
((SSLSocket) socket).setSSLParameters(sslParameters);
}
if ((null != hostnameVerifier) &&
(!hostnameVerifier.verify(host, ((SSLSocket) socket).getSession()))) {
String message = String.format(
"The connection to '%s' failed ssl/tls hostname verification.", host);
throw new JedisConnectionException(message);
}
}
//获取socket对象的输入输出流
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException("Failed connecting to host "
+ host + ":" + port, ex);
}
}
}
向Redis服务端发送命令
Jedis和Client类通过Connection对象的sendCommand()方法进行命令传输,在传输命令时必须将字符串转为字节数组,如果类似于ping,ask这样的命令,只需要将命令转为字节即可。转换完成之后写入到输出流中,将字节流发送给Redis 服务端。
代码3:发送命令
public void sendCommand(final ProtocolCommand cmd) {
sendCommand(cmd, EMPTY_ARGS);
}
public void sendCommand(final ProtocolCommand cmd, final String... args) {
final byte[][] bargs = new byte[args.length][];
for (int i = 0; i < args.length; i++) {
bargs[i] = SafeEncoder.encode(args[i]);
}
sendCommand(cmd, bargs);
}
public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {
try {
connect();
Protocol.sendCommand(outputStream, cmd, args);
} catch (JedisConnectionException ex) {
/*
* When client send request which formed by invalid protocol, Redis send back error message
* before close connection. We try to read it to provide reason of failure.
*/
try {
String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
if (errorMessage != null && errorMessage.length() > 0) {
ex = new JedisConnectionException(errorMessage, ex.getCause());
}
} catch (Exception e) {
/*
* Catch any IOException or JedisConnectionException occurred from InputStream#read and just
* ignore. This approach is safe because reading error message is optional and connection
* will eventually be closed.
*/
}
// Any other exceptions related to connection?
broken = true;
throw ex;
}
}