自己负责的项目redis服务端连接数明显高于实际访问量,很多空闲连接没有释放;Jedis对象和连接的对应关系?连接池的复用是复用了jedis对象,还是只保存连接?总总疑惑,让我开始了jedis的源码阅读。所幸最后都搞明白了,在这里写明白分享给大家。
我相信很多刚入门的同学一定想了解连接池是如何复用连接的。客户端和服务器之间的连接和客户端对象之间的关系。因此在源码解读前,我先提出几个问题,让大家思考一下,并且带着问题在我的文章中寻找答案。
1.我通过JedisPool#getResouce()拿到客户端后,我所有的redis操作应该是基于这一个对象,还是在每次使用的使用的时候都getResource()?如何才能真的用好连接池,实现连接的复用?
2.如果我客户端对象空闲后,一直没有被gc回收,那么与其相关的连接是不是一直浪费?
jedis的连接池是基于apache.common.pool2开发的。所以在关于池的实现都是基于pool2的,关于池中对象的管理是由pool2实现,这里有我写的关于pool2的源码阅读,读者可以跟这篇文件一块阅读,已使能对jedis的实现有全面详细的了解。 apache-common-pool2源码分析
JedisPool是common-pool2的Pool这个抽象类的子类,JedisPool的构造函数最终调用了Pool#initPool方法,根据配置信息实例化pool2中的GenericeObjectPool这个对象池的管理者,这个在介绍common-pool2的源码有做具体分析。
前文已已经提到对象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); } } }
归还池的处理规则逻辑还是交由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也会把他从空闲池和总池中移除。
jedis连接池已经为jedis这个对象做了池的缓存处理,所以我认为业务代码中就不要缓存了,每次都调用getResouce()获取就行了。
使用完毕后,直接调用的JedisPool的returnResource()方法归还池即可。同时也可以调用Jedis#close()方法。最终也是调用了池的归还方法。
config.setMinEvictableIdleTimeMillis()设置空闲连接的最低存活时间。
config.setTimeBetweenEvictionRunsMillis();设置清除空闲连接执行周期。
其他都比较好理解了。
清除空闲连接也会把jedis是对jedis客户端的清除。
如果业务端将其持有不会gc,及时socket被清除断开也不用担心,因为jedis发送命令会校验连接状态,如果已经断开会重新创建连接的。
到这文中最开始提出的两个问题的答案应该就很清晰了。
1.jedis对象连接池会给我们管理,我们自己就不要在管理了。
2.jedis客户端没有被gc,如果我们设置了空闲连接清除,这是时候在达到清除标准后连接会被清除,不浪费资源,当我们再次使用该对象发送命令的时候回重新建立建立。