tomcat jdbc数据库连接池详解之PoolCleaner

2022-03-29 14:41:14 浏览数 (1)

PoolCleaner是一个定时任务,该任务在创建线程池时自动启动,该任务定期执行哪些工作呢,带着这个问题进入源码:

代码语言:javascript复制
        @Override
        public void run() {
            ConnectionPool pool = this.pool.get();
            if (pool == null) {
                stopRunning();
            } else if (!pool.isClosed() &&
                    (System.currentTimeMillis() - lastRun) > sleepTime) {
                lastRun = System.currentTimeMillis();
                try {
                    if (pool.getPoolProperties().isRemoveAbandoned())
                        pool.checkAbandoned();
                    if (pool.getPoolProperties().getMinIdle() < pool.idle
                            .size())
                        pool.checkIdle();
                    if (pool.getPoolProperties().isTestWhileIdle())
                        pool.testAllIdle();
                } catch (Exception x) {
                    log.error("", x);
                }
            }
        }

而checkAbandoned、checkIdle、testAllIdle的执行是受条件约束的,比如checkAbandoned必须在连接池属性removeAbandoned配置为true时才会执行,更多tomcat jdbc连接池配置请参考官网:http://tomcat.apache.org/tomcat-8.5-doc/jdbc-pool.html

下面将一一介绍checkAbandoned、checkIdle、testAllIdle三个任务到底做了些什么工作,

代码语言:javascript复制
  /**

checkAbandoned会清除一些使用时间过长的数据库连接,

要注意的是这部分工作针对的就是使用中的数据库连接,及ConnectionPool类BlockingQueue<PooledConnection> busy属性!!

**/

  public void checkAbandoned() {
        try {
            if (busy.size()==0) return;
            Iterator<PooledConnection> locked = busy.iterator();
            int sto = getPoolProperties().getSuspectTimeout();
            while (locked.hasNext()) {
                PooledConnection con = locked.next();
                boolean setToNull = false;
                try {

                    //操作之前加锁
                    con.lock();
                    //该连接已被移至idle队列中
                    if (idle.contains(con))
                        continue;
                    long time = con.getTimestamp();
                    long now = System.currentTimeMillis();
                    if (shouldAbandon() && (now - time) > con.getAbandonTimeout()) {
                       //从busy队列中移除该连接
                       busy.remove(con);

                        //废弃该连接
                        abandon(con);
                        setToNull = true;
                    } else if (sto > 0 && (now - time) > (sto*1000)) {
                        //将该连接列为疑似要被废弃状态并发送废弃消息
                        suspect(con);
                    } else {
                        //do nothing
                    } //end if
                } finally {
                    con.unlock();
                    if (setToNull)
                        con = null;
                }
            } //while
        } catch (ConcurrentModificationException e) {
            log.debug("checkAbandoned failed." ,e);
        } catch (Exception e) {
            log.warn("checkAbandoned failed, it will be retried.",e);
        }
    }

    protected boolean shouldAbandon() {
        if (poolProperties.getAbandonWhenPercentageFull()==0) return true;
        float used = busy.size();
        float max  = poolProperties.getMaxActive();
        float perc = poolProperties.getAbandonWhenPercentageFull();
        return (used/max*100f)>=perc;
    }

shouldAbandon方法busy状态的连接是否可以被废弃,被废弃需要满足以下条件:

busy态连接数/最大允许存活量>最大允许的存活比例

代码语言:javascript复制
    //该任务针对的是idle队列的连接,即ConnectionPool的BlockingQueue<PooledConnection> idle属性
 public void checkIdle(boolean ignoreMinSize) {

        //ignoreMinSize传入值为false
        try {
            if (idle.size()==0) return;
            long now = System.currentTimeMillis();
            Iterator<PooledConnection> unlocked = idle.iterator();
            //当前idle队列数目大于连接池设置的minIdle值,需要释放多余的空间连接
            while ( (ignoreMinSize || (idle.size()>=getPoolProperties().getMinIdle())) && unlocked.hasNext()) {
                PooledConnection con = unlocked.next();
                boolean setToNull = false;
                try {
                    con.lock();
                    //the con been taken out, we can't clean it up
                    //idle跟busy队列中数据可以互相转换,到底怎么转换的后期文章将会介绍
                    if (busy.contains(con))
                        continue;
                    long time = con.getTimestamp();
                    //具体看下面的shouldReleaseIdle方法
                    if (shouldReleaseIdle(now, con, time)) {
                        release(con);
                        idle.remove(con);
                        setToNull = true;
                    } else {
                        //do nothing
                    } //end if
                } finally {
                    con.unlock();
                    if (setToNull)
                        con = null;
                }
            } //while
        } catch (ConcurrentModificationException e) {
            log.debug("checkIdle failed." ,e);
        } catch (Exception e) {
            log.warn("checkIdle failed, it will be retried.",e);
        }

    }


    protected boolean shouldReleaseIdle(long now, PooledConnection con, long time) {
        if (con.getConnectionVersion() < getPoolVersion()) return true;
        //getReleaseTime返回的是连接池的minEvictableIdleTimeMillis配置,这个参数决定了一个连接在被废弃前所允许的最大idle时间
        else return (con.getReleaseTime()>0) && ((now - time) > con.getReleaseTime()) && (getSize()>getPoolProperties().getMinIdle());
    }
代码语言:javascript复制
   //在空闲期检测所有的idle队列中连接
   public void testAllIdle() {
        try {
            if (idle.size()==0) return;
            Iterator<PooledConnection> unlocked = idle.iterator();
            while (unlocked.hasNext()) {
                PooledConnection con = unlocked.next();
                try {
                    con.lock();
                    //the con been taken out, we can't clean it up
                    if (busy.contains(con))
                        continue;
                    //如果该连接检验失败则可以从idle队列移除,并释放该数据库连接,由于使用数据库连接池的连接是从idle队列中获取的,为了保证连接的有效性需要定期检测这些连接,有些连接可能会被数据库服务端关闭,如果不校验就使用,很可能会使用到无效的连接!
                    if (!con.validate(PooledConnection.VALIDATE_IDLE)) {
                        idle.remove(con);
                        release(con);
                    }
                } finally {
                    con.unlock();
                }
            } //while
        } catch (ConcurrentModificationException e) {
            log.debug("testAllIdle failed." ,e);
        } catch (Exception e) {
            log.warn("testAllIdle failed, it will be retried.",e);
        }

    }

0 人点赞