Redis: Jedis 源代码剖析1-链接建立和收发命令


Jedis作为Redis  Java语言推荐的客户端被广泛使用。让我们一探Jedis源代码究竟。

我们以如下代码来DEBUG观察Jedis源代码:

		 //创建Redis客户端
		 Jedis jedis = new Jedis();
		 //调用set 命令,返回状态标记
		 String code=jedis.set("s", "s");
		 System.out.println("code="+code);
		 //调用get命令
		 String s =jedis.get("s");
		 System.out.println("s="+s);
第一部分:Jedis对象的创建
Jedis jedis=new Jedis();主要是创建链接Redis服务器的客户端。

在Jedis(BinaryJedis)基类中主要有Connection对象。在创建Jedis对象的时候,其实尚未链接到Redis服务器。

在Connection类中,主要设置了链接Redis所使用socket的参数以及操作socket所使用的工具。

//Jedis客户端链接,使用原始socket进行链接
public class Connection implements Closeable 
{
  
  private static final byte[][] EMPTY_ARGS = new byte[0][];
  //默认主机
  private String host = Protocol.DEFAULT_HOST;
  //默认端口
  private int port = Protocol.DEFAULT_PORT;
  //原始socket
  private Socket socket;
  //输入输出流
  private RedisOutputStream outputStream;
  private RedisInputStream inputStream;
  private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
  private int soTimeout = Protocol.DEFAULT_TIMEOUT;
  private boolean broken = false;

  public Connection() {
  }
}

由Connection的成员变量中可以看出来。Jedis使用了元素的JDK IO  Socket来处理网络通信的。

到此,Jedis 对象就创建处理啦。


2-Jedis链接Redis服务器过程。

在调用  String code=jedis.set("s", "s"); 命令的时候,才是真正创建链接的过程。

Client(BinaryClient).set(byte[], byte[])   方法参数就是把由String 字符串转换成字节数值。

并调用Client(Connection).sendCommand(ProtocolCommand, byte[]...) 方法来发送Redis命令。

  //每次发送命令前都判断是否链接,如果链接端口并且链接不上,则抛出异常
  protected Connection sendCommand(final ProtocolCommand cmd, final byte[]... args) {
    try {
      connect();//每次发送Redis命令都会调用Connect()方法来链接Redis远程服务器
      Protocol.sendCommand(outputStream, cmd, args); //操作socket 的输出流来发送命令
      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;
    }
  }
每次调用sendCommand发送命令时候,都会调用Connnect()方法尝试链接远程端口。

  //在发送命令之前连接redis服务器
  public void connect() {
    if (!isConnected()) {
      try {
    	//创建新socket  
        socket = new Socket();
        //设置socket参数
        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);
        //获取socket原始输入输出流
        outputStream = new RedisOutputStream(socket.getOutputStream());
        inputStream = new RedisInputStream(socket.getInputStream());
      } catch (IOException ex) {
        broken = true;
        throw new JedisConnectionException(ex);
      }
    }
  }

每次链接到远程Redis服务器后,第一个命令就是发送密钥命令。

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

在每次发送一个命令后,都会去获取返回码。

  /**  对于Value值存在最大范围上线。
   * 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
   */
  @Override
  public String set(final String key, String value) {
    checkIsInMultiOrPipeline();
    client.set(key, value);
    return client.getStatusCodeReply();
  }

在获取状态码时,每次都去刷新通道。

  public String getStatusCodeReply() {
    flush();
    final byte[] resp = (byte[]) readProtocolWithCheckingBroken();
    if (null == resp) {
      return null;
    } else {
      return SafeEncoder.encode(resp);
    }
  }

读取数据流最终通过SocketInputStream 类来读取。

以上就涉及到Jedis发送Redis和接受Redis命令的过程。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Jedis使用总结 前段时间细节的了解了Jedis的使用,Jedisredis的java版本的客户端实现。 本文做个总结,主要分享如下内容: 【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】 好了,一个一个来。 一、 Pipeline 官方的说明是:starts a pipeline,which is a very efficient way to send lots of command and read all the responses when you finish sending them。简单点说pipeline适用于批处理。当有大量的操作需要一次性执行的时候,可以用管道。 示例: Jedis jedis = new Jedis(String, int); Pipeline p = jedis.pipelined(); p.set(key,value);//每个操作都发送请求给redis-server p.get(key,value); p.sync();//这段代码获取所有的response 这里我进行了20w次连续操作(10w读,10w写),不用pipeline耗时:187242ms,用pipeline耗时:1188ms,可见使用管道后的性能上了一个台阶。看了代码了解到,管道通过一次性写入请求,然后一次性读取响应。也就是说jedis是:request response,request response,...;pipeline则是:request request... response response的方式。这样无需每次请求都等待server端的响应。 二、 跨jvm的id生成器 谈到这个话题,首先要知道redis-server端是单线程来处理client端的请求的。 这样来实现一个id生成器就非常简单了,只要简单的调用jdeis.incr(key);就搞定了。 你或许会问,incr是原子操作吗,能保证不会出现并发问题吗,不是说了吗,server端是单线程处理请求的。 三、 【跨jvm的锁实现【watch】【multi】】 首先说下这个问题的使用场景,有些时候我们业务逻辑是在不同的jvm进程甚至是不同的物理机上的jvm处理的。这样如何来实现不同jvm上的同步问题呢,其实我们可以基于redis来实现一个锁。 具体事务和监听请参考文章:redis学习笔记之事务 暂时找到三种实现方式: 1. 通过jedis.setnx(key,value)实现 import java.util.Random; import org.apache.commons.pool.impl.GenericObjectPool.Config; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; /** * @author Teaey */ public class RedisLock { //加锁标志 public static final String LOCKED = "TRUE"; public static final long ONE_MILLI_NANOS = 1000000L; //默认超时时间(毫秒) public static final long DEFAULT_TIME_OUT = 3000; public static JedisPool pool; public static final Random r = new Random(); //锁的超时时间(秒),过期删除 public static final int EXPIRE = 5 * 60; static { pool = new JedisPool(new Config(), "host", 6379); } private Jedis jedis; private String key; //锁状态标志 private boolean locked = false; public RedisLock(String key) { this.key = key; this.jedis = pool.getResource(); } public boolean lock(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { if (jedis.setnx(key, LOCKED) == 1) { jedis.expire(key, EXPIRE); locked = true; return locked; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } public boolean lock() { return lock(DEFAULT_TIME_OUT); } // 无论是否加锁成功,必须调用 public void unlock() { try { if (locked) jedis.del(key); } finally { pool.returnResource(jedis); } } } 2. 通过事务(multi)实现 由于采纳第一张方法,第二种跟第三种实现只贴了关键代码,望谅解。^_^ public boolean lock_2(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { Transaction t = jedis.multi(); // 开启事务,当server端收到multi指令 // 会将该client的命令放入一个队列,然后依次执行,知道收到exec指令 t.getSet(key, LOCKED); t.expire(key, EXPIRE); String ret = (String) t.exec().get(0); if (ret == null || ret.equals("UNLOCK")) { return true; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 3. 通过事务+监听实现 public boolean lock_3(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { jedis.watch(key); // 开启watch之后,如果key的值被修改,则事务失败,exec方法返回null String value = jedis.get(key); if (value == null || value.equals("UNLOCK")) { Transaction t = jedis.multi(); t.setex(key, EXPIRE, LOCKED); if (t.exec() != null) { return true; } } jedis.unwatch(); // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 最终采用第一种实现,因为加锁只需发送一个请求,效率最高。 四、 【redis分布式】 最后一个话题,jedis的分布式。在jedis源码里发现了两种hash算法(MD5,MURMUR Hash(默认)),也可以自己实现redis.clients.util.Hashing接口扩展。 List<JedisShardInfo> hosts = new ArrayList<JedisShardInfo>(); //server1 JedisShardInfo host1 = new JedisShardInfo("", 6380, 2000); //server2 JedisShardInfo host2 = new JedisShardInfo("", 6381, 2000); hosts.add(host1); hosts.add(host2); ShardedJedis jedis = new ShardedJedis(hosts); jedis.set("key", "");
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值