自己负责的项目redis服务端连接数明显高于实际访问量,很多空闲连接没有释放;Jedis对象和连接的对应关系?连接池的复用是复用了jedis对象,还是只保存连接?总总疑惑,让我开始了jedis的源码阅读。所幸最后都搞明白了,在这里写明白分享给大家。
我相信很多刚入门的同学一定想了解连接池是如何复用连接的。客户端和服务器之间的连接和客户端对象之间的关系。因此在源码解读前,我先提出几个问题,让大家思考一下,并且带着问题在我的文章中寻找答案。
1.我通过JedisPool#getResouce()拿到客户端后,我所有的redis操作应该是基于这一个对象,还是在每次使用的使用的时候都getResource()?如何才能真的用好连接池,实现连接的复用?
2.如果我客户端对象空闲后,那么与其相关的连接是不是一直浪费?
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 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继承自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这个对象被销毁了,他的一个属性client及socket连接一块销毁。
归还池的处理规则逻辑还是交由common-pool2实现的,可以那篇文章中获取。其实就是把其放到空闲队列里,如果队列满了,就直接销毁。销毁的实现也是在JedisFactory
@Override
public void destroyObject(PooledObject 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()方法。最终也是调用了池的归还方法。
通过JedisConfig进行相关设置只是对客户端的管理设置,跟服务没有关系。
config.setMinEvictableIdleTimeMillis()设置空闲连接的最低存活时间。
config.setTimeBetweenEvictionRunsMillis();设置清除空闲连接执行周期。
其他都比较好理解了。
清除空闲连接也会把jedis是对jedis客户端的清除。
如果业务端将其持有,及时socket被清除断开也不用担心,因为jedis发送命令会校验连接状态,如果已经断开会重新创建连接的。 看到源码中是有判断链接是否可用,但是判断的方法是socket中的值,而该值的维护是通过主动调用socket的close等方法对其产生影响,所以server端主动断开链接jedis是感知不到的,代码层通过isConnected判断依旧为true,依然使用该链接,接下来自然就会报错。所以要准确的设置最大空闲时间,保证跟server端的空闲时间同步。
到这文中最开始提出的两个问题的答案应该就很清晰了。
1.jedis对象连接池会给我们管理,我们自己就不要在管理了。
2.jedis客户端没有被销毁,如果我们设置了空闲连接清除,这是时候在达到清除标准后连接会被清除,不浪费资源,当我们再次使用该对象发送命令的时候回重新建立建立。