Jedis源码浅析

1、概述

 

Jedis是redis官网推荐的redis java client,代码维护在github https://github.com/xetorthio/jedis

本质上Jedis帮我们封装了各种redis命令,提供了各种和redis命令相关的方法使用。Jedis的基本结构如下图1.1所示。

clipboard

图1.1 Jedis 工作过程

可以看到Jedis通过socket和redis server通信,通过发送redis命令和参数,接收redis server的结果,从而实现redis client。

 

2、源码解析

 

按照Jedis源码分为两个层次分析,分别是:以Jedis为核心和以JedisPool为核心。

先看一下Jedis jar包的层次结构,如下图2.1所示,下面都是以 jedis-2.9.0 为例。

clipboard

图2.1 Jedis Jar包层次结构

可以看到,Jedis Jar包结构简单,主要有两个package:redis.clients.jedis 和 redis.clients.util。jedis包下主要是和jedis相关的核心类,util包下是jedis会用到的各种工具类。需要注意的是:jedis依赖apache 的 commons-pool2 包。

2.1 Jedis

使用Jedis连接redis server的Java代码如下:

 

//连接本地的 Redis 服务
Jedis jedis = new Jedis("localhost", 6379); 
jedis.set("key","val");

 

一行代码就能完成redis的连接,从而开始使用redis。

先看一下Jedis这个类图,如下图2.2所示。

clipboard

图2.2 Jedis 类图

上图可以看到,Jedis类继承了1个类,实现了7个接口。实现了BinaryJedis类,该类主要用于处理二进制数据,Socket发送给redis server的数据是二进制的。分别实现了JedisCommands、MultiKeyCommands、BasicCommands、ScriptingCommands、SentinelCommands、AdvancedJedisCommands、ClusterCommands接口,从接口名上可以看到,这些接口都声明了不同场景下的redis命令方法。JedisCommands声明了常用的redis命令方法,包含redis五种数据接口的相关操作方法;MultiKeyCommands声明了常用的redis批量操作方法;BasicCommands声明了redis基本系统操作方法;ScriptingCommands声明了redis 相关脚本命令方法;SentinelCommands声明了redis在哨兵模式下的相关命令方法;ClusterCommands声明了redis集群模式下的相关命令方法;AdvancedJedisCommands声明了redis的一些高级命令方法,包含redis配置等。

下面来分析一下Jedis的代码,其代码如下:

 

public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
    AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands {

  protected Pool<Jedis> dataSource = null;
  // Jedis提供了很多不同的构造方法,提供了各种接入手段,方便开发
  // Jedis本质上通过调用父类BinaryJedis的构造器来完成初始化操作
  public Jedis() {
    super();
  }

  public Jedis(final String host) {
    super(host);
  }

  public Jedis(final String host, final int port) {
    super(host, port);
  }
  
  ...
 
  // 下面是redis各种命令的封装方法
  // 本质上都是调用父类 BinaryJedis 中定义的 client 来完成各种操作
  /**
   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
   * GB).
   * <p>
   * Time complexity: O(1)
   * @param key
   * @param value
   * @return Status code reply
   */
  public String set(final String key, String value) {
    checkIsInMultiOrPipeline();
    client.set(key, value);
    return client.getStatusCodeReply();
  }

  /**
   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
   * GB).
   * @param key
   * @param value
   * @param nxxx NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
   *          if it already exist.
   * @param expx EX|PX, expire time units: EX = seconds; PX = milliseconds
   * @param time expire time in the units of <code>expx</code>
   * @return Status code reply
   */
  public String set(final String key, final String value, final String nxxx, final String expx,
      final long time) {
    checkIsInMultiOrPipeline();
    client.set(key, value, nxxx, expx, time);
    return client.getStatusCodeReply();
  }
  
  ...
  
}

 

从上面可以看到Jedis本质上通过父类BinaryJedis中定义的 client来完成各种redis操作。BinaryJedis的代码如下:

 

public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
    AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
  // 用于和redis通信的客户端实例
  protected Client client = null;
  protected Transaction transaction = null;
  protected Pipeline pipeline = null;
  
   // BinaryJedis提供了许多构造方法,用于初始化,
   // Jedis类也是通过调用父类BinaryJedis的构造方法完成初始化
  // BinaryJedis构造方法中本质上是在初始化Clent实例
  public BinaryJedis() {
    client = new Client();
  }

  public BinaryJedis(final String host) {
    URI uri = URI.create(host);
    if (uri.getScheme() != null && uri.getScheme().equals("redis")) {
      initializeClientFromURI(uri);
    } else {
      client = new Client(host);
    }
  }

  public BinaryJedis(final String host, final int port) {
    client = new Client(host, port);
  }

  public BinaryJedis(final String host, final int port, final boolean ssl) {
    client = new Client(host, port, ssl);
  }

  public BinaryJedis(final String host, final int port, final boolean ssl,
      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
      final HostnameVerifier hostnameVerifier) {
    client = new Client(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
  }

  public BinaryJedis(final String host, final int port, final int timeout) {
    client = new Client(host, port);
    client.setConnectionTimeout(timeout);
    client.setSoTimeout(timeout);
  }

  public BinaryJedis(final String host, final int port, final int timeout, final boolean ssl) {
    client = new Client(host, port, ssl);
    client.setConnectionTimeout(timeout);
    client.setSoTimeout(timeout);
  }

  public BinaryJedis(final String host, final int port, final int timeout, final boolean ssl,
      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
      final HostnameVerifier hostnameVerifier) {
    client = new Client(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
    client.setConnectionTimeout(timeout);
    client.setSoTimeout(timeout);
  }

  public BinaryJedis(final String host, final int port, final int connectionTimeout,
      final int soTimeout) {
    client = new Client(host, port);
    client.setConnectionTimeout(connectionTimeout);
    client.setSoTimeout(soTimeout);
  }

  public BinaryJedis(final String host, final int port, final int connectionTimeout,
      final int soTimeout, final boolean ssl) {
    client = new Client(host, port, ssl);
    client.setConnectionTimeout(connectionTimeout);
    client.setSoTimeout(soTimeout);
  }
  
  ...
}

 

通过BinaryJedis代码可以看到,Jedis主要通过Client来完成具体redis操作,Client的代码如下:

 

public class Client extends BinaryClient implements Commands {
 
  // Client类的构造器调用了父类BinaryClient的构造器
  public Client() {
    super();
  }

  public Client(final String host) {
    super(host);
  }

  public Client(final String host, final int port) {
    super(host, port);
  }

  public Client(final String host, final int port, final boolean ssl) {
    super(host, port, ssl);
  }

  public Client(final String host, final int port, final boolean ssl,
      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
      final HostnameVerifier hostnameVerifier) {
    super(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
  }

  // 封装了各种redis命令方法,本质上调用了父类BinaryClient中的对应方法
  @Override
  public void set(final String key, final String value) {
    set(SafeEncoder.encode(key), SafeEncoder.encode(value));
  }

  public void set(final String key, final String value, final String nxxx, final String expx,
      final long time) {
    set(SafeEncoder.encode(key), SafeEncoder.encode(value), SafeEncoder.encode(nxxx),
      SafeEncoder.encode(expx), time);
  }

  public void get(final String key) {
    get(SafeEncoder.encode(key));
  }

  public void exists(final String key) {
    exists(SafeEncoder.encode(key));
  }

  public void exists(final String... keys) {
    final byte[][] bkeys = SafeEncoder.encodeMany(keys);
    exists(bkeys);
  }

  public void del(final String... keys) {
    final byte[][] bkeys = new byte[keys.length][];
    for (int i = 0; i < keys.length; i++) {
      bkeys[i] = SafeEncoder.encode(keys[i]);
    }
    del(bkeys);
  }
  ...
  
}

 

从上可以看到,Client类定义了封装redis命令的方法,本质上调用的父类BinaryClient中的方法,BinaryClient代码如下:

 

public class BinaryClient extends Connection {
  public enum LIST_POSITION {
    BEFORE, AFTER;
    public final byte[] raw;

    private LIST_POSITION() {
      raw = SafeEncoder.encode(name());
    }
  }

  private boolean isInMulti;

  private String password;

  private long db;

  private boolean isInWatch;

  // Binary提供了各种构造器方法
  public BinaryClient() {
    super();
  }

  public BinaryClient(final String host) {
    super(host);
  }

  public BinaryClient(final String host, final int port) {
    super(host, port);
  }

  public BinaryClient(final String host, final int port, final boolean ssl) {
    super(host, port, ssl);
  }

  public BinaryClient(final String host, final int port, final boolean ssl,
      final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
      final HostnameVerifier hostnameVerifier) {
    super(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
  }

  public boolean isInMulti() {
    return isInMulti;
  }

  public boolean isInWatch() {
    return isInWatch;
  }
  
  private byte[][] joinParameters(byte[] first, byte[][] rest) {
    byte[][] result = new byte[rest.length + 1][];
    result[0] = first;
    System.arraycopy(rest, 0, result, 1, rest.length);
    return result;
  }

  public void setPassword(final String password) {
    this.password = password;
  }

  public void setDb(long db) {
    this.db = db;
  }

  @Override
  public void connect() {
    if (!isConnected()) {
      super.connect();
      if (password != null) {
        auth(password);
        getStatusCodeReply();
      }
      if (db > 0) {
        select(Long.valueOf(db).intValue());
        getStatusCodeReply();
      }
    }
  }

  // 所有的redis命令方法本质上调用的父类Connection中的sendCommand方法
  public void ping() {
    sendCommand(Command.PING);
  }

  public void set(final byte[] key, final byte[] value) {
    sendCommand(Command.SET, key, value);
  }

  public void set(final byte[] key, final byte[] value, final byte[] nxxx, final byte[] expx,
      final long time) {
    sendCommand(Command.SET, key, value, nxxx, expx, toByteArray(time));
  }

  public void get(final byte[] key) {
    sendCommand(Command.GET, key);
  }

  public void quit() {
    db = 0;
    sendCommand(QUIT);
  }

  public void exists(final byte[]... key) {
    sendCommand(EXISTS, key);
  }
  
  ...
  
}

 

上面可以看到,BinaryClient中的redis命令方法本质上调用的是父类Connection中的sendCommand方法,下面看一下Connection代码。

 

public class Connection implements Closeable {

  private static final byte[][] EMPTY_ARGS = new byte[0][];
 
  // redis server host & port
  private String host = Protocol.DEFAULT_HOST;
  private int port = Protocol.DEFAULT_PORT;
  // 用于和redis server通信的socket
  private Socket socket;
  // 用于发送和接收数据的io流对象
  private RedisOutputStream outputStream;
  private RedisInputStream inputStream;
  private int pipelinedCommands = 0;
  private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
  private int soTimeout = Protocol.DEFAULT_TIMEOUT;
  private boolean broken = false;
  private boolean ssl;
  private SSLSocketFactory sslSocketFactory;
  private SSLParameters sslParameters;
  private HostnameVerifier hostnameVerifier;

  // Connection类提供了各种不同的构造方法
  public Connection() {
  }

  public Connection(final String host) {
    this.host = host;
  }

  public Connection(final String host, final int port) {
    this.host = host;
    this.port = port;
  }

  public Connection(final String host, final int port, final boolean ssl) {
    this.host = host;
    this.port = port;
    this.ssl = ssl;
  }

  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;
  }

  public Socket getSocket() {
    return socket;
  }

  public int getConnectionTimeout() {
    return connectionTimeout;
  }

  public int getSoTimeout() {
    return soTimeout;
  }

  public void setConnectionTimeout(int connectionTimeout) {
    this.connectionTimeout = connectionTimeout;
  }

  public void setSoTimeout(int soTimeout) {
    this.soTimeout = soTimeout;
  }

  public void setTimeoutInfinite() {
    try {
      if (!isConnected()) {
        connect();
      }
      socket.setSoTimeout(0);
    } catch (SocketException ex) {
      broken = true;
      throw new JedisConnectionException(ex);
    }
  }

  public void rollbackTimeout() {
    try {
      socket.setSoTimeout(soTimeout);
    } catch (SocketException ex) {
      broken = true;
      throw new JedisConnectionException(ex);
    }
  }
  
  // redis命令调用方法,本质上调用的都是sendCommand通用方法
  protected Connection sendCommand(final Command 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]);
    }
    return sendCommand(cmd, bargs);
  }

  protected Connection sendCommand(final Command cmd) {
    return sendCommand(cmd, EMPTY_ARGS);
  }
  
  ...

  // 具体和redis通信,发送命令的方法
  protected Connection sendCommand(final Command cmd, final byte[]... args) {
      try {
        // 1. 建立redis连接  
        connect();
        // 2. 使用Protocol.sendCommand方法发送redis命令和参数
        Protocol.sendCommand(outputStream, cmd, args);
        pipelinedCommands++;
        return this;
      } 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;
      }
    }
    
    ...
    
    // 和redis server 建立连接,本质上使用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);
    
          if (ssl) {
            if (null == sslSocketFactory) {
              sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
            }
            socket = (SSLSocket) 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连接的io流对象
          outputStream = new RedisOutputStream(socket.getOutputStream());
          inputStream = new RedisInputStream(socket.getInputStream());
        } catch (IOException ex) {
          broken = true;
          throw new JedisConnectionException(ex);
        }
      }
    }
    
    ...
    
}

 

通过Connection代码可以看到,其调用了 Prototcol 的 sendCommand 方法来发送redis命令和参数。Protocol代码如下。

 

public final class Protocol {

  private static final String ASK_RESPONSE = "ASK";
  private static final String MOVED_RESPONSE = "MOVED";
  private static final String CLUSTERDOWN_RESPONSE = "CLUSTERDOWN";
  private static final String BUSY_RESPONSE = "BUSY";
  private static final String NOSCRIPT_RESPONSE = "NOSCRIPT";
  
  // 默认主机号和端口号
  public static final String DEFAULT_HOST = "localhost";
  public static final int DEFAULT_PORT = 6379;
  public static final int DEFAULT_SENTINEL_PORT = 26379;
  public static final int DEFAULT_TIMEOUT = 2000;
  public static final int DEFAULT_DATABASE = 0;
  // 支持的编码格式
  public static final String CHARSET = "UTF-8";

  // 通用命令常量
  public static final byte DOLLAR_BYTE = '$';
  public static final byte ASTERISK_BYTE = '*';
  public static final byte PLUS_BYTE = '+';
  public static final byte MINUS_BYTE = '-';
  public static final byte COLON_BYTE = ':';

  public static final String SENTINEL_MASTERS = "masters";
  public static final String SENTINEL_GET_MASTER_ADDR_BY_NAME = "get-master-addr-by-name";
  public static final String SENTINEL_RESET = "reset";
  public static final String SENTINEL_SLAVES = "slaves";
  public static final String SENTINEL_FAILOVER = "failover";
  public static final String SENTINEL_MONITOR = "monitor";
  public static final String SENTINEL_REMOVE = "remove";
  public static final String SENTINEL_SET = "set";

  public static final String CLUSTER_NODES = "nodes";
  public static final String CLUSTER_MEET = "meet";
  public static final String CLUSTER_RESET = "reset";
  public static final String CLUSTER_ADDSLOTS = "addslots";
  public static final String CLUSTER_DELSLOTS = "delslots";
  public static final String CLUSTER_INFO = "info";
  public static final String CLUSTER_GETKEYSINSLOT = "getkeysinslot";
  public static final String CLUSTER_SETSLOT = "setslot";
  public static final String CLUSTER_SETSLOT_NODE = "node";
  public static final String CLUSTER_SETSLOT_MIGRATING = "migrating";
  public static final String CLUSTER_SETSLOT_IMPORTING = "importing";
  public static final String CLUSTER_SETSLOT_STABLE = "stable";
  public static final String CLUSTER_FORGET = "forget";
  public static final String CLUSTER_FLUSHSLOT = "flushslots";
  public static final String CLUSTER_KEYSLOT = "keyslot";
  public static final String CLUSTER_COUNTKEYINSLOT = "countkeysinslot";
  public static final String CLUSTER_SAVECONFIG = "saveconfig";
  public static final String CLUSTER_REPLICATE = "replicate";
  public static final String CLUSTER_SLAVES = "slaves";
  public static final String CLUSTER_FAILOVER = "failover";
  public static final String CLUSTER_SLOTS = "slots";
  public static final String PUBSUB_CHANNELS = "channels";
  public static final String PUBSUB_NUMSUB = "numsub";
  public static final String PUBSUB_NUM_PAT = "numpat";

  public static final byte[] BYTES_TRUE = toByteArray(1);
  public static final byte[] BYTES_FALSE = toByteArray(0);

  private Protocol() {
    // this prevent the class from instantiation
  }

  public static void sendCommand(final RedisOutputStream os, final Command command,
      final byte[]... args) {
    sendCommand(os, command.raw, args);
  }

  // 完成redis命令和数据的发送
  private static void sendCommand(final RedisOutputStream os, final byte[] command,
      final byte[]... args) {
    try {
      os.write(ASTERISK_BYTE);
      os.writeIntCrLf(args.length + 1);
      os.write(DOLLAR_BYTE);
      os.writeIntCrLf(command.length);
      os.write(command);
      os.writeCrLf();

      for (final byte[] arg : args) {
        os.write(DOLLAR_BYTE);
        os.writeIntCrLf(arg.length);
        os.write(arg);
        os.writeCrLf();
      }
    } catch (IOException e) {
      throw new JedisConnectionException(e);
    }
  }
  
  ...
  
}

 

Jedis中通过继承File的IO流程类定义了redis 操作的IO流,包含RedisOutputStream 和 RedisInputStream,用于定义redis的一些定制化操作。

 

通过上面的分析,举一个例子,比如:

jedis.set("key", "value") 本质上会给 redis server 发送字节流 *3\r\n$1\r\rnset\r\n$3\n\rkey\r\n$5\n\rvalue\r\n

 

下面来分析上面过程中的其他细节。

a. 字符串转字节数组

字节转数组工具类定义在 redis.clients.util 包下。

 

public final class SafeEncoder {
  private SafeEncoder(){
    throw new InstantiationError( "Must not instantiate this class" );
  }

  public static byte[][] encodeMany(final String... strs) {
    byte[][] many = new byte[strs.length][];
    for (int i = 0; i < strs.length; i++) {
      many[i] = encode(strs[i]);
    }
    return many;
  }
  
  // 字符串编码为字节数组
  public static byte[] encode(final String str) {
    try {
      if (str == null) {
        throw new JedisDataException("value sent to redis cannot be null");
      }
      return str.getBytes(Protocol.CHARSET);
    } catch (UnsupportedEncodingException e) {
      throw new JedisException(e);
    }
  }

  public static String encode(final byte[] data) {
    try {
      return new String(data, Protocol.CHARSET);
    } catch (UnsupportedEncodingException e) {
      throw new JedisException(e);
    }
  }
}

 

从上面可以看到,通过调用JDK中的 getBytes 方法获取字节数组,同时指定编码格式UTF-8。

b. 命令定义

Jedis用到的所有redis命令和关键字都以枚举形式定义在Protocol类中,代码如下:

 

public final class Protocol {
    
    ...
    // redis命令枚举
    public static enum Command {
      PING, SET, GET, QUIT, EXISTS, DEL, TYPE, FLUSHDB, KEYS, RANDOMKEY, RENAME, RENAMENX, RENAMEX, DBSIZE, EXPIRE, EXPIREAT, TTL, SELECT, MOVE, FLUSHALL, GETSET, MGET, SETNX, SETEX, MSET, MSETNX, DECRBY, DECR, INCRBY, INCR, APPEND, SUBSTR, HSET, HGET, HSETNX, HMSET, HMGET, HINCRBY, HEXISTS, HDEL, HLEN, HKEYS, HVALS, HGETALL, RPUSH, LPUSH, LLEN, LRANGE, LTRIM, LINDEX, LSET, LREM, LPOP, RPOP, RPOPLPUSH, SADD, SMEMBERS, SREM, SPOP, SMOVE, SCARD, SISMEMBER, SINTER, SINTERSTORE, SUNION, SUNIONSTORE, SDIFF, SDIFFSTORE, SRANDMEMBER, ZADD, ZRANGE, ZREM, ZINCRBY, ZRANK, ZREVRANK, ZREVRANGE, ZCARD, ZSCORE, MULTI, DISCARD, EXEC, WATCH, UNWATCH, SORT, BLPOP, BRPOP, AUTH, SUBSCRIBE, PUBLISH, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBSUB, ZCOUNT, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZUNIONSTORE, ZINTERSTORE, ZLEXCOUNT, ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYLEX, SAVE, BGSAVE, BGREWRITEAOF, LASTSAVE, SHUTDOWN, INFO, MONITOR, SLAVEOF, CONFIG, STRLEN, SYNC, LPUSHX, PERSIST, RPUSHX, ECHO, LINSERT, DEBUG, BRPOPLPUSH, SETBIT, GETBIT, BITPOS, SETRANGE, GETRANGE, EVAL, EVALSHA, SCRIPT, SLOWLOG, OBJECT, BITCOUNT, BITOP, SENTINEL, DUMP, RESTORE, PEXPIRE, PEXPIREAT, PTTL, INCRBYFLOAT, PSETEX, CLIENT, TIME, MIGRATE, HINCRBYFLOAT, SCAN, HSCAN, SSCAN, ZSCAN, WAIT, CLUSTER, ASKING, PFADD, PFCOUNT, PFMERGE, READONLY, GEOADD, GEODIST, GEOHASH, GEOPOS, GEORADIUS, GEORADIUSBYMEMBER, BITFIELD;
    
      public final byte[] raw;
    
      Command() {
        raw = SafeEncoder.encode(this.name());
      }

    }

    // redis 关键字
    public static enum Keyword {
      AGGREGATE, ALPHA, ASC, BY, DESC, GET, LIMIT, MESSAGE, NO, NOSORT, PMESSAGE, PSUBSCRIBE, PUNSUBSCRIBE, OK, ONE, QUEUED, SET, STORE, SUBSCRIBE, UNSUBSCRIBE, WEIGHTS, WITHSCORES, RESETSTAT, RESET, FLUSH, EXISTS, LOAD, KILL, LEN, REFCOUNT, ENCODING, IDLETIME, AND, OR, XOR, NOT, GETNAME, SETNAME, LIST, MATCH, COUNT, PING, PONG;
      public final byte[] raw;
    
      Keyword() {
        raw = SafeEncoder.encode(this.name().toLowerCase(Locale.ENGLISH));
      }
    }
}

 

2.2. JedisPool

在第1部分中已经提到,Jedis依赖apache 的 commons-pool2 的jar包。jedis主要使用commons-pool2 来实现jedis连接池。下面分析一下jedis如何使用 commons-pool2 实现一个jedis连接池。下图3.1为连接池实现类图。

 

clipboard

图3.1 JedisPool 实现类图

JedisPool实现了Pool类,Pool类是jedis中定义的一个工具类,该类中有类型为GenericObjectPool的成员变量,GenericObjectPool是apache commons-pool2中连接池的一种实现。GenericObjectPool构造器有两个入参,分别是连接池配置类 BaseObjectPoolConfig 和PooledObjectFactory 池中对象生成工厂类。Jedis针对apache commons-pool2 中的PooledObjectFactory实现了jedis的池内对象生成工厂类。池内对象生成工厂类生成的对象类型是PooledObject,其有实现类DefaultPooledObject。

对于GenericObjectPool的实现细节,后续写一篇解析一下。

再来看一下 GenericObjectPoolConfig 代码中的默认配置。

 

public class GenericObjectPoolConfig extends BaseObjectPoolConfig {

    ...

    /**
     * The default value for the {@code maxTotal} configuration attribute.
     * @see GenericObjectPool#getMaxTotal()
     */
     // 默认最大池大小
    public static final int DEFAULT_MAX_TOTAL = 8;

    /**
     * The default value for the {@code maxIdle} configuration attribute.
     * @see GenericObjectPool#getMaxIdle()
     */
     // 默认池中最大空闲对象数
    public static final int DEFAULT_MAX_IDLE = 8;

    /**
     * The default value for the {@code minIdle} configuration attribute.
     * @see GenericObjectPool#getMinIdle()
     */
     // 默认池中最小空闲对象数
    public static final int DEFAULT_MIN_IDLE = 0;

    private int maxTotal = DEFAULT_MAX_TOTAL;

    private int maxIdle = DEFAULT_MAX_IDLE;

    private int minIdle = DEFAULT_MIN_IDLE;
    
    ...
    
}

 

3、 总结

 

通过上面的源码解析,基本了解了Jedis的工作原理。Jedis作为使用Java语言实现的Redis客户端,原理是通过socket 和 redis server通信,传输redis命令和参数。同时Jedis借助apache commons-pool2 实现了redis连接池。

posted @ 2019-11-07 21:28  锢浪熟阳  阅读(1388)  评论(1编辑  收藏  举报