转载

Redis客户端Jedis源码阅读及连接池分析

0.起因

自己负责的项目redis服务端连接数明显高于实际访问量,很多空闲连接没有释放;Jedis对象和连接的对应关系?连接池的复用是复用了jedis对象,还是只保存连接?总总疑惑,让我开始了jedis的源码阅读。所幸最后都搞明白了,在这里写明白分享给大家。

我相信很多刚入门的同学一定想了解连接池是如何复用连接的。客户端和服务器之间的连接和客户端对象之间的关系。因此在源码解读前,我先提出几个问题,让大家思考一下,并且带着问题在我的文章中寻找答案。

1.我通过JedisPool#getResouce()拿到客户端后,我所有的redis操作应该是基于这一个对象,还是在每次使用的使用的时候都getResource()?如何才能真的用好连接池,实现连接的复用?

2.如果我客户端对象空闲后,一直没有被gc回收,那么与其相关的连接是不是一直浪费?

1.源码分析

jedis的连接池是基于apache.common.pool2开发的。所以在关于池的实现都是基于pool2的,关于池中对象的管理是由pool2实现,这里有我写的关于pool2的源码阅读,读者可以跟这篇文件一块阅读,已使能对jedis的实现有全面详细的了解。 apache-common-pool2源码分析

1.1连接池JedisPool的创建

JedisPool是common-pool2的Pool这个抽象类的子类,JedisPool的构造函数最终调用了Pool#initPool方法,根据配置信息实例化pool2中的GenericeObjectPool这个对象池的管理者,这个在介绍common-pool2的源码有做具体分析。

1.2创建获取客户端Jedis

前文已已经提到对象Jedis是缓存到common-pool2的对象池,具体的获取规则在common中有说明。

这里重点说明一下Jedis是如何真正的实例化吧。

JedisFactory实现了common的PooledObjectFactory接口(池中对象创建和销毁的接口,交由业务方(就是文中的JedisFactory来实现))。

在JedisFactory#makeObject()方法中

public PooledObject<Jedis> makeObject() throws Exception {
    final HostAndPort hostAndPort = this.hostAndPort.get();
    final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout,
        soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);//实现化

    try {
      jedis.connect();//跟redis服务建立tcp连接,下面是检验
      if (password != null) {
        jedis.auth(password);
      }
      if (database != 0) {
        jedis.select(database);
      }
      if (clientName != null) {
        jedis.clientSetname(clientName);
      }
    } catch (JedisException je) {
      jedis.close();
      throw je;
    }

    return new DefaultPooledObject<Jedis>(jedis);

  }

Jedis继承自BinaryJedis,其有一个Client属性,Client是Connection的子类,Connection中有socket这个属性,也就是真正跟redis服务端创建连接的类,并且这个socket是个长连接

public void connect() {
    if (!isConnected()) {
      try {
        socket = new Socket();
        // ->@wjw_add
        socket.setReuseAddress(true);
        socket.setKeepAlive(true); // Will monitor the TCP connection is
       //省略代码

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

从这就能看到Jedis这个对象是和网络连接是一一绑定的,如果redis这个对象被销毁gc了,他的一个属性client及socket连接一块销毁。

1.3 客户端归还池

归还池的处理规则逻辑还是交由common-pool2实现的,可以那篇文章中获取。其实就是把其放到空闲队列里,如果队列满了,就直接销毁。销毁的实现也是在JedisFactory

@Override
  public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {
    final BinaryJedis jedis = pooledJedis.getObject();
    if (jedis.isConnected()) {
      try {
        try {
          jedis.quit();
        } catch (Exception e) {
        }
        jedis.disconnect();
      } catch (Exception e) {

      }
    }

  }

可以看到是主动关闭socket连接,在common-pool2中的GenericObjectPool也会把他从空闲池和总池中移除。

2.jedis api使用和总结

jedis连接池已经为jedis这个对象做了池的缓存处理,所以我认为业务代码中就不要缓存了,每次都调用getResouce()获取就行了。

使用完毕后,直接调用的JedisPool的returnResource()方法归还池即可。同时也可以调用Jedis#close()方法。最终也是调用了池的归还方法。

通过JedisConfig进行相关设置只是对客户端的管理设置,跟服务没有关系。

config.setMinEvictableIdleTimeMillis()设置空闲连接的最低存活时间。

config.setTimeBetweenEvictionRunsMillis();设置清除空闲连接执行周期。

其他都比较好理解了。

清除空闲连接也会把jedis是对jedis客户端的清除。

如果业务端将其持有不会gc,及时socket被清除断开也不用担心,因为jedis发送命令会校验连接状态,如果已经断开会重新创建连接的。

到这文中最开始提出的两个问题的答案应该就很清晰了。

1.jedis对象连接池会给我们管理,我们自己就不要在管理了。

2.jedis客户端没有被gc,如果我们设置了空闲连接清除,这是时候在达到清除标准后连接会被清除,不浪费资源,当我们再次使用该对象发送命令的时候回重新建立建立。

原文  http://blog.csdn.net/u013592964/article/details/78632439
正文到此结束
Loading...