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 (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();
  }
在调用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,开销很大。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值