现状
之前公司的自管理监控项目可以实时监测Redis的运行状态,来检查Redis是否是正常运行的,但是最近经常出现一些问题,就是明明Redis集群是正常的,但是界面上却看不到Redis的运行状态,这就很奇怪了,于是看了下后台服务日志内容,报错内容大致如下
报错内容
2021-09-17 16:01:24.577 ERROR c.u.b.s.c.a.m.r.RedisStatusDetecter - redis detect error
redis.clients.jedis.exceptions.JedisExhaustedPoolException: Could not get a resource since the pool is exhausted
at redis.clients.jedis.util.Pool.getResource(Pool.java:53)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:288)
at com.ultrapower.bsms.selfmanage.collector.activedetect.middleware.redis.RedisCollector.doCollect(RedisCollector.java:35)
at com.ultrapower.bsms.selfmanage.collector.activedetect.middleware.redis.RedisStatusDetecter.detect(RedisStatusDetecter.java:35)
at com.ultrapower.bsms.selfmanage.collector.ScheduleTasks.redisDetecter(ScheduleTasks.java:182)
at sun.reflect.GeneratedMethodAccessor318.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at
翻译成中文大致意思就是取不到Redis连接了,因为连接池已经耗尽了,那问题是正常情况下从JedisPool里面获取的redis连接应该会在使用完就会释放,现在给人的感觉就是连接池一直在取redis连接,但是使用完后并没有释放,最终导致连接池中的连接全部用完耗尽,当再也取不到连接的时候就会报上面这个错,而代码处理逻辑就是根据redis连接去获取Redis的活动状态,因为这里获取不到连接,就会认为Redis挂掉,进而就从数据库把redis的节点信息给删除了,所以界面上看不到redis了
根据这里报错的源头找到之前项目里这块代码有这么一段,可以看到这块通过jedisPool获取redis连接后进行了一波信息处理后就直接返回结果了,,,难道说jedisPool有自动释放redis连接的功能呢???还是说这是前人代码留下来的一个坑。。。。
Jedis jedis =null;
jedis = jedisPool.getResource();
String info = jedis.info();
//log.info("meKey=" + meKey);
List<Map<String, String>> kpiDataList = getNodeData(info);
result.put(meKey, kpiDataList);
return result;
在JedisPool的老版本,也就是3.0以前的版本中,关闭释放连接是这么做的
可以看到这里做了两种操作,一种操作是获取连接发生异常的时候调用returnBrokenResource()方法处理,另外一种方式是正常调用,之后在finally里面调用returnResource去释放连接,看来考虑情况还挺多的,,,
public String get(String key) {
Jedis redis = null;
try {
redis = redisPool.getResource();
return redis.get(key);
} catch (JedisConnectionException e) {
if (redis != null) {
redisPool.returnBrokenResource(redis);
redis = null;
}
throw e;
} finally {
if (redis != null) {
redisPool.returnResource(redis);
}
}
}
一开始想用这种方式处理,但是貌似效果不好使。。。于是看了下公司的redie的版本,是3.0以上的版本,难道说3.0以上版本的释放连接功能变了,于是各种一顿查,说是在3.0以上版本直接调用close方法就可以了。。。。我去这么简单粗暴的嘛
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
于是代码直接改造成如下这种,看着是不是比之前释放连接方式简介多了,但是为什么比之前这么简洁呢。。。
// 每个节点详细指标信息
Jedis jedis =null;
try{
jedis = jedisPool.getResource();
String info = jedis.info();
//log.info("meKey=" + meKey);
List<Map<String, String>> kpiDataList = getNodeData(info);
result.put(meKey, kpiDataList);
}finally {
//新增jedis释放连接的方法,避免运行一段时间后redis连接池耗尽而挂掉
if(jedis!=null){
jedis.close();
}
}
}
return result;
于是处于好奇点进去看了下close方法,好家伙,原来官方在这里做了一层封装处理,内部其实还是换汤不换药,但是对于使用者去调用可是友好了很多,这点得点个赞
在改造完毕后重新启动项目,运行了很长一段时间,再也没有报Redis连接池耗尽的问题,。完美解决了
结论
使用JedisPool连接池获取redis连接池一定要记得主动关闭redis连接,避免这种低级错。。。。这又算填了前人的一个坑哈哈