Jedis源码分析

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;
    }
  }
原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11375662.html