• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    解决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 {
    ThreadLocalShardedJedis> jedisHolder = new ThreadLocalShardedJedis>();
    
    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连接无法正常释放的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

    您可能感兴趣的文章:
    • 基于SpringBoot2.0默认使用Redis连接池的配置操作
    • Java连接redis及基本操作示例
    • Redis分布式锁python-redis-lock使用方法
    上一篇:Redis fork进程分配不到内存解决方案
    下一篇:Centos 7 如何安装Redis(推荐)
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    解决Redis连接无法正常释放的问题 解决,Redis,连接,无法,正常,