Invalidated object not currently part of this pool/Could not return the resource to the pool

这两天使用ApacheBench(ab)对接口进行压力测试的时候,多线程环境下一直报错

redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
    at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:100)
    at redis.clients.jedis.JedisPool.returnBrokenResource(JedisPool.java:112)
    at com.finger.shop.common.utils.RedisCacheUtil.returnBorkenConnection(RedisCacheUtil.java:187)
    at com.finger.shop.common.utils.RedisCacheUtil.set(RedisCacheUtil.java:518)
    at com.finger.shop.common.utils.RedisCacheUtil.set(RedisCacheUtil.java:485)
    at com.finger.shop.service.impl.SyncServiceImpl.insertRedis(SyncServiceImpl.java:187)
    at com.finger.shop.service.impl.SyncServiceImpl.insertInfo(SyncServiceImpl.java:94)
    at com.finger.shop.service.impl.SyncServiceImpl.syncInfo(SyncServiceImpl.java:62)
    ....................
Caused by: java.lang.IllegalStateException: Invalidated object not currently part of this pool
    at org.apache.commons.pool2.impl.GenericObjectPool.invalidateObject(GenericObjectPool.java:640)
    at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:98)
    ... 85 common frames omitted

初始代码:

public static RedisCacheUtil getInstance(RedisConfiguration redisConfiguration) {
        if (INSTANCE == null) {
            synchronized (RedisCacheUtil.class) {
                if (INSTANCE == null) {
                    INSTANCE = new RedisCacheUtil();
                }
            }
        }

       JedisPoolConfig config = new JedisPoolConfig();
       config.setMaxIdle(Integer.valueOf(30));
       config.setTestOnBorrow(Boolean.valueOf(true));
       jedisPool = new JedisPool(config, redisConfiguration.getHost(), Integer.valueOf(redisConfiguration.getPort()), 0, redisConfiguration.getPassword());
        return INSTANCE;
    }

修改后的代码:

@PostConstruct
private static synchronized JedisPool createJedisPool(RedisConfiguration redisConfiguration) {
        if (jedisPool == null) {

            JedisPoolConfig config = new JedisPoolConfig();

            config.setMaxIdle(Integer.valueOf(30));
            config.setTestOnBorrow(Boolean.valueOf(true));

            jedisPool = new JedisPool(config, redisConfiguration.getHost(), Integer.valueOf(redisConfiguration.getPort()), 0, redisConfiguration.getPassword());
        }

        return jedisPool;
    }
    /**
     * 获取实例
     *
     * @return
     */
    public static /*synchronized*/ RedisCacheUtil getInstance(RedisConfiguration redisConfiguration) {
        if (INSTANCE == null) {
            synchronized (RedisCacheUtil.class) {
                if (INSTANCE == null) {
                    INSTANCE = new RedisCacheUtil();
                }
            }
        }

        if (jedisPool == null) {
            createJedisPool(redisConfiguration);
        }

        return INSTANCE;
    }

在网上搜了很长时间,终于找到了解决方案,作者研究的很透彻:

分析

该错误大致意思是说:不能将redis连接放回池内,放回连接池的对象是无效的对象。在网上查了很多同类错误,都说是进行了两次returnResource释放连接资源造成的,因为第一次return成功以后,第二次return就会报上面这个错误。但是显然,我翻遍了代码并没有两次调用returnResource。
查看redis服务端的连接数详细信息如下,前9个连接,idle=453,空闲了453秒了,依然没有释放,而连接池设置的是空闲60秒就会被释放,明显发生异常了。
Invalidated object not currently part of this pool/Could not return the resource to the pool_第1张图片
初步怀疑是多线程执行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已经不为空了,直接返回。现在多线程安全的问题就得以解决。
解决前后对比:
Invalidated object not currently part of this pool/Could not return the resource to the pool_第2张图片
参考:http://www.voidcn.com/blog/li396864285/article/p-6318599.html

你可能感兴趣的:(noSql)