JedisPool 在关闭情况下为什么 还能提供连接资源 并且 只有一个线程一直拿不到连接资源

Richard_Yi
  • 2.1k

问题描述

昨日在产品发版的时候,项目的启动之后,有个线程(下面称之为线程A)一直在抛jedis异常,异常信息如下

线程A会随着程序启动一直运行之后,每10s会去缓存中取一次数据,报错就是在取数据的时候发生的。

以下是jedisDao类,get(key)的代码

    @Autowire
    private JedisPool jedisPool;
    
    @Override
    public String get(String key) {
        Jedis jedis = null;
        String string;
        try {
            jedis = jedisPool.getResource();
            string = jedis.get(key);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return string;
    }

神奇的是,整个业务没有受影响,其他的线程都能够从这个jedisPool中获取连接资源。只有线程A一直在抛这个异常。

jedisPoolConfig

  <!-- 连接池配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大连接数 -->
        <property name="maxTotal" value="30"/>
        <!-- 最大空闲连接数 -->
        <property name="maxIdle" value="10"/>
        <!-- 每次释放连接的最大数目 -->
        <property name="numTestsPerEvictionRun" value="1024"/>
        <!-- 释放连接的扫描间隔(毫秒) -->
        <property name="timeBetweenEvictionRunsMillis" value="30000"/>
        <!-- 连接最小空闲时间 -->
        <property name="minEvictableIdleTimeMillis" value="1800000"/>
        <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
        <property name="softMinEvictableIdleTimeMillis" value="10000"/>
        <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
        <property name="maxWaitMillis" value="1500"/>
        <!-- 在获取连接的时候检查有效性, 默认false -->
        <property name="testOnBorrow" value="true"/>
        <!-- 在空闲时检查有效性, 默认false -->
        <property name="testWhileIdle" value="true"/>
        <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
        <property name="blockWhenExhausted" value="false"/>
    </bean>

临时解决方案

调大了最大连接数,重启了程序,这个报错就不见了。

        <!-- 最大连接数 -->
        <property name="maxTotal" value="50"/>

初步探索和疑问

我先是就 Pool not open的报错google了一下,github jedis的issue下面有相关的提问

Could not get a resource from the pool #1159

意思就是说,在取资源的时候,jedisPool 处于被关闭的状态。看了下相关的源码,首先是jedisPool.getResource()

 @Override
  public Jedis getResource() {
    Jedis jedis = super.getResource();
    jedis.setDataSource(this);
    return jedis;
  }

// super.getResource();
public T getResource() {
    try {
      return internalPool.borrowObject();
    } catch (NoSuchElementException nse) {
      throw new JedisException("Could not get a resource from the pool", nse);
    } catch (Exception e) {
      throw new JedisConnectionException("Could not get a resource from the pool", e);
    }
  }
// 定义在GenericObjectPool中的borrowObject()
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
        // 在这里报了错
        assertOpen();

        AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
                (getNumIdle() < 2) &&
                (getNumActive() > getMaxTotal() - 3) ) {
            removeAbandoned(ac);
        }
    ...
}

// GenericObjectPool#assertOpen()
final void assertOpen() throws IllegalStateException {
    if (isClosed()) {
        throw new IllegalStateException("Pool not open");
    }
}
public final boolean isClosed() {
    return closed;
}

如issue里说的,jedisPool实际上是去内部成员internalPool中borrowObject - 获取连接资源,在我的案例中,线程A获取连接资源的时候,这个内部连接池的内部变量closed = true,导致抛出了如上图的异常。

那么就有几个疑问了

  1. internalPool这个连接池对象是在什么情况下被close掉的呢?我看了下代码,只有在调用jedisPool的destroy()或者close()方法的时候,内部变量closed 才会被设为true。但是我检查了项目代码,没有地方显式调用了destroy()或者close()方法。难道是jedis自己内部的某块逻辑做了这个操作?

    请问各位大神是否有了解呢?

  2. 既然jedisPool目前的状态是closed,为什么其他操作缓存的操作是正常的呢?都是会调用s上面提到的jedisDao类的get(key)
  3. 由于是生产环境上出现的,我暂时没有如何重现的这个bug的思路,可否麻烦各路大神给我提供下本地重现的思路?
  4. 重启之后就没有这个问题了,这又是为什么呢?是因为调大了连接数,然后线程A启动的时候就拿到了连接所以就没事情了?那也说不通啊,其他操作缓存的线程最后都是会调用jedis.close的方法的,连接资源应该会释放才对。为什么之前一直拿不到?

小弟我对jedis的了解暂浅,希望能借此机会能和社区各位前辈请教下经验,希望能得到你们宝贵的意见。

如果有什么我需要补充的信息,也请告诉我。

回复
阅读 2.1k
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏