解决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⑼ 把redis连接1⑼ 归还给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连接没法正常释放的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
文章来源:丸子建站
文章标题:解决Redis连接没法正常释放的问题
https://www.wanzijz.com/view/58429.html