解决Redis连接无法正常释放的问题
错误信息:
illegalstateexception: invalidated object not currently part of this pool
一、问题描述
前些天用多线程执行操作测试验证vanyar-redis连接池,应用是刚重启的状态,执行操作是,开启10个线程同时执行10000次操作。
如下:
执行操作完毕后发现控制台输出9个下面错误信息:
该错误大致意思是说:不能将redis连接放回池内,放回连接池的对象是无效的对象。在网上查了很多同类错误,都说是进行了两次returnresource释放连接资源造成的,因为第一次return成功以后,第二次return就会报上面这个错误。但是显然,我翻遍了代码并没有两次调用returnresource。
查看redis服务端的连接数详细信息如下,前9个连接,idle=453,空闲了453秒了,依然没有释放,而连接池设置的是空闲60秒就会被释放,明显发生异常了。
初步怀疑是多线程执行redis操作,初始化redis连接池有问题。于是重启应用,先执行单线程redis操作,再执行多线程redis操作,没有发生上面的问题。redis服务端连接均能正常释放。由此得出结论,当线程池在未初始化的时候,由于多线程同时执行redis连接池初始化工作引起的问题。
看代码(redisjedispool未优化之前):当10个线程同时请求redis连接资源时,10个线程都发现连接池为空(因为创建连接池相比创建线程比较耗时),这时10个线程都各自初始化成功一个连接池,并从中取得redis连接,并执行了redis操作。执行完毕,returnresource的时候,由于此时pool变量的引用是最后一个线程初始化的连接池,前面9个线程获得的redis连接并不属于最后一个连接池的资源,所以抛错:illegalstateexception: invalidated object not currently part of this pool
二、报错原因分析
线程1 : 创建redis连接池1 : 获得redis连接1
线程2 : 创建redis连接池2 : 获得redis连接2
线程3 : 创建redis连接池3 : 获得redis连接3
……
线程8 : 创建redis连接池8 : 获得redis连接8
线程9 : 创建redis连接池9 : 获得redis连接9
线程10 : 创建redis连接池10 : 获得redis连接10
全局变量pool引用 指向 redis连接池10
当线程1-9 把redis连接1-9 归还给pool-redis连接池10
reds连接池10自然就报错,说:
illegalstateexception: invalidated object not currently part of this pool
三、解决办法
由于创建线程池,连接池等工作都是相对比较耗时的,所以我们一般放在应用启动的时候就初始化,把连接池的初始化工作交给spring容器管理,同时把初始化连接池和获取连接两个操作实现方法分离,对初始化连接池的方法加上同步锁机制,并且二次判断是否为空,就算多线程情况下,在二次判断是否为空的时候,pool已经不为空了,直接返回。现在多线程安全的问题就得以解决。
附上,解决前后对比图:
补充知识:java spring框架中方法级redis的连接自动获取和释放实现
java中使用redis总是需要处理redis连接的获取,释放等操作,每次使用都会使代码变的特别丑陋,模仿spring中aop的实现,用动态代理写一个 连接自动获取和释放的工具
主要思路
jedismanagesupport 抽象类 类似于 aop的切入点,所有继承了该类(一般都是service层)的类,可以使用提供的获取redis的方法获取redis,并且不需要释放
jedisbeanpostprocessor 继承beanpostprocessor ,会在bean初始化时执行自己定义的逻辑:
如果a类继承了 jedismanagesupport ,就会获取redis连接并且放到jedismanagesupport 的成员变量里,a类的实例(其实是cglib动态代理生成的
a类的子类的实例)就可以使用该redis连接 进行相关操作了
代理类的实例见源码
源码如下
public class jedisbeanpostprocessor implements beanpostprocessor { @autowired shardedjedispool shardedjedispool; static final logger logger = logger.getlogger(jedisbeanpostprocessor.class); @override public object postprocessafterinitialization(object bean, string beanname) throws beansexception { if (bean instanceof jedismanagesupport) { enhancer enhancer = new enhancer(); enhancer.setsuperclass(bean.getclass()); enhancer.setcallback(new jedisinterceptor(shardedjedispool, bean)); object targetbean = enhancer.create(); return targetbean; } else { return bean; } } @override public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { return bean; } } class jedisinterceptor implements methodinterceptor { static final logger logger = logger.getlogger(jedisinterceptor.class); shardedjedispool pool; object src; public jedisinterceptor(shardedjedispool pool, object src) { this.pool = pool; this.src = src; } @override public object intercept(object target, method method, object[] arguments, methodproxy methodproxy) throws throwable { object result = null; if (target instanceof jedismanagesupport) { if (this.isdeclaredmethod(target, method)) { shardedjedis jedis = null; try { jedismanagesupport support = (jedismanagesupport) src; jedis = pool.getresource(); support.setshardedjedis(jedis); // logger.debug("调用之前注入jedis对象,method:" + method); /** * 下面代码可以使用 method.invoke(src,arguments)。 不能使用 * methodproxy.invokesuper(target,arguments); * 因为a类中用autowired注入的属性,生成代理的子类b后,因为子类b是新建的类。从父类继承的属性没有被初始化, * 使用methodproxy.invokesuper()执行是,会报空指针异常. */ result = methodproxy.invoke(src, arguments); support.setshardedjedis(null); } catch (exception e) { pool.returnbrokenresource(jedis); e.printstacktrace(); } finally { if (jedis != null) { pool.returnresource(jedis); } // logger.debug("调用之后归还jedis对象,method:" + method); } } else { result = methodproxy.invoke(src, arguments); } } else { throw new exception("使用该代理必须继承jedismanagesupport"); } return result; } /** * 是否是target类本身定义的非私有的方法,还是继承的父类 * @return true是target自己类的并且不是私有的的, */ private boolean isdeclaredmethod(object target, method arg1) { method temp = null; try { temp = target.getclass().getdeclaredmethod(arg1.getname(), arg1.getparametertypes()); } catch (securityexception e) { e.printstacktrace(); } catch (nosuchmethodexception e) { e.printstacktrace(); } /** * 不为null,并且是非私有的,返回true */ if (temp != null) { return true; } else { return false; } } } public abstract class jedismanagesupport { threadlocal<shardedjedis> jedisholder = new threadlocal<shardedjedis>(); public final shardedjedis getshardedjedis() { return jedisholder.get(); } public final void setshardedjedis(shardedjedis jedis) { jedisholder.set(jedis); } /** * 如果某个键不同单位之间也不会重复,可以使用这个方法生成redis的键 */ public final byte[] assemkey(string basekey) { assert.istrue(stringutils.isnotblank(basekey), "参数不能为空"); return basekey.getbytes(); } /** * 根据tablename+prefix 构造唯一key与assemkey(string basekey, string tablename) * 规则一致 */ public final byte[] assemkeybyprefix(string tablename, string basekey) { assert.istrue(stringutils.isnotblank(basekey), "参数不能为空"); assert.istrue(stringutils.isnotblank(tablename), "参数不能为空"); unitinfo unit = webservice.getunitinfo(); assert.istrue(unit != null, "单位信息获取不到"); return (tablename + "-" + unit.getprefix() + "-" + basekey).getbytes(); } /** * * 不同前缀的表中可能有相同的键,同一个表中也可能是有重复的basekey时,用这个生成redis的key 比如 用户信息表的 * username字段,不同的用户信息表允许重复的username,mooc_t_userinfo * 也允许有相同的账号,所以生成redis的key时,需要用到单位的mooc_school 放入redis中 */ public final byte[] assemkeybyfid(string tablename, string basekey) { unitinfo unit = webservice.getunitinfo(); assert.istrue(unit != null, "单位信息获取不到"); return (tablename + "-" + unit.getmoocschool() + "-" + basekey).getbytes(); } }
以上这篇解决redis连接无法正常释放的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
上一篇: python 调整图片亮度的示例
推荐阅读
-
MFC连接数据库时,无法启动程序,计算机丢失libmysql.dll的问题解决办法
-
网络连接正常无法上网的原因及解决办法
-
树莓派3B的WiFi中文乱码问题无法连接_解决方案:
-
如何解决160wifi无法连接(提示无线网卡设备不正常)的问题
-
解决在Linux操作系统下无法连接MySQL服务端的问题
-
Linux中无法远程连接数据库问题的解决方法
-
RedisDesktopManager无法远程连接Redis的完美解决方法
-
Oracle导dmp出现文件ORA-12154: TNS: 无法解析指定的连接标识符问题的解决方案
-
解决Vue 项目打包后favicon无法正常显示的问题
-
Broadcom网卡linux系统下无法连接到网络问题的解决办法