jedis源码分析(一)-jedis对象实现

redis作为高速缓存在我们的开发场景中经常应用,Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
       Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
       Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
       Redis支持数据的备份,即master-slave模式的数据备份。
Redis的优势
    性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
    丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
    原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
    丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

在Java语言中最常用的连接redis服务的客户端包就是jedis,jedis实现对redis操作处理的几个连接类,Jedis、jedisPool、ShardedJedis、ShardedJedisPool,JedisSentinelPool几部分,jedis操作单实例的redis,jedisPool单实例的连接池实现,ShardedJedis操作多节点的redis,即分片存储实现,ShardedJedisPool多实例redis节点分片连接池,JedisSentinelPool哨兵模式连接池,我们通过对这几部分简单应用理解jedis对redis的操作方式。

1,单实例模式:单个redis服务节点,
        通过简单对单节点的jedis对象直接操作demo学习jedis,示例代码:
import redis.clients.jedis.Jedis;
public class JedisUtil {
       private static Jedis jedis = null;
       private final String host;
       private final Integer port;
       private String password;
       public JedisUtil(final String host, final int port) {
              this.host = host;
              this.port = port;
              initJedis();
       }
       public JedisUtil(final String host, final int port, final String password) {
              this.host = host;
              this.port = port;
              this.password = password;
              initJedis();
       }
       public void initJedis() {
              if (jedis == null) {
                     jedis = new Jedis(host, port);
                     System.out.println("创建jedis连接成功");
                     if (password != null) {
                           jedis.auth(password);
                     }
              }
       }
       public void set(String key, String value) {
              try {
                     String result = jedis.set(key, value);
                     System.out.println(result);
                     if ("OK".equals(result)) {
                     }
              } finally {
                     jedis.close();
              }
       }
       
       public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                     JedisUtil jedisUtil = new JedisUtil("127.0.0.1", 4100);
                     jedisUtil.set("test" + i, "value" + i);
              }
       }
}
  
查看jedis类源码,发现jedis继承了BinaryJedis类和实现了很多Command接口,JedisCommands, MultiKeyCommands,AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands。
BinaryJedis类是和redis服务连接的交互,JedisCommands接口定义了通用分片和非分片的set,get等接口,BasicCommands接口定义了ping,quit,flushAll,info等基础命令接口,ClusterCommands接口定义了集群接口,SentinelCommands接口定义哨兵接口

jedis实例化源代码过程
public Jedis(final String host, final int port) {
    super(host, port);
}
public BinaryJedis(final String host, final int port) {
    client = new Client(host, port);
}
 由于jedis继承了BinaryJedis类,在BinaryJedis中实例化了clent对象,client继承BinaryClient,BinaryClient继承了Connection类,
BinayClient中主要是发送命令的操作,connection是跟redis服务交互。

首先看set方法
  /**
   * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1GB).
   * <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();
  }
在调用client的set方法之前,调用了checkIsInMultiOrPipeline();来判断是否设定了mult或者Pipeline模式, 查看手册pipeline 只是把多个redis指令一起发出去,redis并没有保证这些指定的执行是原子的;multi相当于一个redis的transaction的,保证整个操作的原子性,避免由于中途出错而导致最后产生的数据不一致。通过测试得知,pipeline方式执行效率要比其他方式高10倍左右的速度,启用multi写入要比没有开启慢一点。
set方法委托client进行处理
@Override
public void set(final String key, final String value) {
   set(SafeEncoder.encode(key), SafeEncoder.encode(value));
}
掉用了BinaryClient的set方法
public void set(final byte[] key, final byte[] value) {
    sendCommand(Command.SET, key, value);
}
掉用了Connection的sendCommand方法
protected Connection sendCommand(final Command cmd, final byte[]... args) {
              try {
                     connect();
                     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;
              }
       }
1,掉用connection()方法进行连接
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);
                                  }
                           }
                           outputStream = new RedisOutputStream(socket.getOutputStream());
                           inputStream = new RedisInputStream(socket.getInputStream());
                     } catch (IOException ex) {
                           broken = true;
                           throw new JedisConnectionException(ex);
                     }
              }
       }
这里主要使用socket进行通信连接,使用长连接进行通讯减少连接开销,并实例化了RedisOutputStream和RedisInputStream来读取内容
2,掉用protocol的sendCommand方法
  public static void sendCommand(final RedisOutputStream os, final Command command,
      final byte[]... args) {
    sendCommand(os, command.raw, args);
  }
  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);
    }
  }
最后掉用client.getStatusCodeReply();获取返回状态
       public String getStatusCodeReply() {
              flush();
              pipelinedCommands--;
              final byte[] resp = (byte[]) readProtocolWithCheckingBroken();
              if (null == resp) {
                     return null;
              } else {
                     return SafeEncoder.encode(resp);
              }
       }
  getStatusCodeReply方法中首先掉用了flush()方法,保证之前发送的命令能发送出去,之后掉用了readProtocolWithCheckingBroken()方法,
protected Object readProtocolWithCheckingBroken() {
              try {
                     return Protocol.read(inputStream);
              } catch (JedisConnectionException exc) {
                     broken = true;
                     throw exc;
              }
       }
调用Protocol.read进行对RedisInputStream进行读取,
public static Object read(final RedisInputStream is) {
    return process(is);
}

private static Object process(final RedisInputStream is) {
    final byte b = is.readByte();
    if (b == PLUS_BYTE) {
      return processStatusCodeReply(is);
    } else if (b == DOLLAR_BYTE) {
      return processBulkReply(is);
    } else if (b == ASTERISK_BYTE) {
      return processMultiBulkReply(is);
    } else if (b == COLON_BYTE) {
      return processInteger(is);
    } else if (b == MINUS_BYTE) {
      processError(is);
      return null;
    } else {
      throw new JedisConnectionException("Unknown reply: " + (char) b);
    }
  }
最后在read的时候对返回的响应进行了判断,枚举出了几种响应方式,对不同的响应进行不同的处理。
整个交互过程是通过Socket方式通信过程,但是这里也有一个问题就是线程安全问题,显然Jedis实例是线程不安全的,对于多线程共享jedis实例是会有问题的。同时直接使用jedis不能避免的需要反复的创建和销毁Socket,开销很大。


阅读更多
文章标签: redis
下一篇jedis源码分析(二)-JedisPool连接池实现
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭