• mybatics 连接池-Druid


    1 Druid配置参数详解-initialSize

    initialSize是什么意思?
    initialSize:连接池初始化时初始化的数据库连接数
    initialSize在哪个阶段会起作用?
    当项目第一次进行增,删,改,查的时候,连接池会初始化,这个时候会根据initialSize参数初始化数据库连接放入连接池中。
    画外音:这就是为什么第一次进行数据库操作的时候,响应会比较慢的原因,创建数据库连接是很耗时的,所以初始化连接并不是越多越好
    initialSize是怎么起作用的?
    当连接池初始化时,会调用DruidDataSource的init初始化数据库连接

    public void init() throws SQLException {
        if (inited) {
            return;
        }
        //....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
     // init connections
     //poolingCount是连接池中已有连接数
     while (poolingCount < initialSize) {
         try {
                PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
                DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
                connections[poolingCount++] = holder;
           } catch (SQLException ex) {
               LOG.error("init datasource error, url: " + this.getUrl(), ex);
               if (initExceptionThrow) {
                     connectError = ex;
                     break;
               } else {
                   Thread.sleep(3000);
               }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2 minIdle是什么意思?

    minIdle:连接池中的最小空闲连接数,Druid会定时扫描连接池的连接,如果空闲的连接数大于该值,则关闭多余的连接,反之则创建更多的连接以满足最小连接数要求。

    标题为什么要设置这个参数?
    设置这个参数可以应对突发流量,如果没有设置空闲连接,当有多个请求同时调用数据库,但是连接池中并没有可用连接,这时就必须创建连接,创建连接是一个非常耗时的操作,有可能会导致请求超时。

    minIdle是怎么起作用的?
    当连接池初始化时,会初始化一个定时清除空闲连接的任务DestroyTask,该任务默认是1分钟执行一次(使用timeBetweenEvictionRunsMillis参数设置)

    protected void createAndStartDestroyThread() {
            destroyTask = new DestroyTask();
            if (destroyScheduler != null) {
                long period = timeBetweenEvictionRunsMillis;
                if (period <= 0) {
                    period = 1000;
                }
                //定时清除空闲连接的任务,默认1分钟执行一次
                destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyTask, period, period,
                                                                              TimeUnit.MILLISECONDS);
                initedLatch.countDown();
                return;
            }
     
            String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
            destroyConnectionThread = new DestroyConnectionThread(threadName);
            destroyConnectionThread.start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个定时任务中会判断连接池的连接是否满足关闭的条件,如果满足则关闭,满足的条件如下:
    空闲时间大于minEvictableIdleTimeMillis(默认30分钟),并且空闲连接数大于minIdle;
    空闲时间大于maxEvictableIdleTimeMillis(默认7小时);
    //关闭条件,空闲时间大于minEvictableIdleTimeMillis,并且空闲连接大于minIdle,
    // 其中checkCount为poolingCount - minIdle,即可能被关闭的连接数量
    //或者空闲时间大于maxEvictableIdleTimeMillis

    if (idleMillis >= minEvictableIdleTimeMillis) {
            if (checkTime && i < checkCount) {
                   evictConnections[evictCount++] = connection;
                   continue;
            } else if (idleMillis > maxEvictableIdleTimeMillis) {
                  evictConnections[evictCount++] = connection;
                 continue;
            }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当然也有可能这时数据库中的连接数小于minIdle这个时候就需要创建新的连接了

    if (keepAlive && poolingCount + activeCount < minIdle) {
                    needFill = true;
    }
     
    if (needFill) {
                lock.lock();
                try {
                    int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
                    for (int i = 0; i < fillCount; ++i) {
                        //通知CreateConnectionTask创建连接
                        emptySignal();
                    }
                } finally {
                    lock.unlock();
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    总结
    minIdle的意义是预防突发流量;
    数据源会在DestroyTask定时检查池中的空闲连接,会关闭多余的连接或者创建新的连接。
    数据源并不能确保空闲连接一定等于minIdle,可以通过设置timeBetweenEvictionRunsMillis参数调整定时任务的执行间隔,从而控制数据源中空闲连接数更加接近minIdle;

    3 Druid配置参数详解-maxActive

    maxActive是什么意思?
    maxActive:连接池中的最大连接数,连接池中的连接包含3部分:
    activeCount:正在使用的连接;
    poolingCount:连接池中的空闲连接;
    createTaskCount:正在生成的连接;
    这三部分的连接总和不能超过maxActive;

    为什么要设置这个参数?
    数据库的连接总数是有限制的,有时候僧多粥少,只能限制每个应用的连接数了。
    maxActive是怎么起作用的?
    maxActive在Druid中有多处使用,最主要的一处是在CreateConnectionTask中
    // 防止创建超过maxActive数量的连接

      if (activeCount + poolingCount >= maxActive) {
                 clearCreateTask(taskId);
                 return;
      }
    
    • 1
    • 2
    • 3
    • 4

    4 maxWait是什么意思?

    maxWait:从连接池中获取连接的最大等待时间,单位ms,默认-1,即会一直等待下去
    为什么要设置这个参数?
    笔者在使用Druid时都会设置这个参数,这样如果是获取连接超时,更容易从日志中获取调用失败的原因。
    如果超时,Druid会抛出以下异常

    Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 3000, active 4, maxActive 4, creating 0
        at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1722)
        at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1402)
        at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5059)
        at com.alibaba.druid.filter.logging.LogFilter.dataSource_getConnection(LogFilter.java:886)
        at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
        at com.alibaba.druid.filter.FilterAdapter.dataSource_getConnection(FilterAdapter.java:2756)
        at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
        at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:680)
        at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1380)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1372)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:109)
        at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:262)
        ... 11 more
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    maxWait是怎么起作用的?
    在DruidDataSource中的getConnectionInternal方法使用到了maxWait

    private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
        if (closed) {
            connectErrorCountUpdater.incrementAndGet(this);
            throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
        }
    }
    
        //此处省略部分代码
        if (maxWait > 0) {
             //获取连接,如果超时将返回null
              holder = pollLast(nanos);
        } else {
              holder = takeLast();
        }
                
        //此处省略部分代码
        //如果超时,则holder返回null,然后抛出异常
        if (holder == null) {
            long waitNanos = waitNanosLocal.get();
    
            StringBuilder buf = new StringBuilder(128);
            buf.append("wait millis ")//
               .append(waitNanos / (1000 * 1000))//
               .append(", active ").append(activeCount)//
               .append(", maxActive ").append(maxActive)//
               .append(", creating ").append(creatingCount)//
            ;
            if (creatingCount > 0 && createStartNanos > 0) {
                long createElapseMillis = (System.nanoTime() - createStartNanos) / (1000 * 1000);
                if (createElapseMillis > 0) {
                    buf.append(", createElapseMillis ").append(createElapseMillis);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    总结
    maxWait默认是不 m超时,即如果连接池没有空闲连接,则会一直等待下去,但是一般的接口都是有超时时间的,如果接口超时,不方便定位出来是获取不到连接导致的,最好设置maxWait,并且小于接口的超时时间。

    5 Druid配置参数详解-timeBetweenEvictionRunsMillis

    timeBetweenEvictionRunsMillis是什么意思?
    timeBetweenEvictionRunsMillis默认值是60s,主要作用在两处地方
    作为DestroyTask执行的时间周期,DestroyTask主要有两个作用:

    判断连接池的连接空闲数是否大于minIdle,如果是则关闭多余的连接,反之则新增连接,具体可以参见

    回收连接池泄露的连接,具体可以参见
    作为验证连接是否有效的时间周期,如果testOnBorrowfalse并且testWhileIdletrue,则在应用获取连接的时候会判断连接的空闲时间是否大于timeBetweenEvictionRunsMillis,如果大于则会验证该连接是否有效。

    public class DestroyConnectionThread extends Thread {、;
    
        public DestroyConnectionThread(String name){
            super(name);
            this.setDaemon(true);
        }
    
        public void run() {
            initedLatch.countDown();
    
            for (;;) {
                // 从前面开始删除
                try {
                    if (closed) {
                        break;
                    }
    
                    if (timeBetweenEvictionRunsMillis > 0) {
                        Thread.sleep(timeBetweenEvictionRunsMillis);
                    } else {
                        Thread.sleep(1000); //
                    }
    
                    if (Thread.interrupted()) {
                        break;
                    }
    
                    destroyTask.run();
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    总结
    timeBetweenEvictionRunsMillis可以用来控制空闲连接数的回收周期
    timeBetweenEvictionRunsMillis可以用来控制回收泄露连接的周期
    timeBetweenEvictionRunsMillis连接的空闲时间大于该值testWhileIdle才起作用

    6 Druid配置参数详解-testWhileIdle

    testWhileIdle是什么意思?
    testWhileIdle:如果为true(默认true),当应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用。

    testWhileIdle什么时候会起作用?
    获取连接时;
    testOnBorrowfalse;
    testWhileIdle
    true;

    使用代码在DruidDataSource的getConnectionDirect方法
    注意:此时判断连接空闲的依据是空闲时间大于timeBetweenEvictionRunsMillis(默认1分钟),并不是使用minEvictableIdleTimeMillis跟maxEvictableIdleTimeMillis,也就是说如果连接空闲时间超过一分钟就测试一下连接的有效性,但并不是直接剔除;而如果空闲时间超过了minEvictableIdleTimeMillis则会直接剔除。

                if (testOnBorrow) {
                    boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                    if (!validate) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("skip not validate connection.");
                        }
    
                        discardConnection(poolableConnection.holder);
                        continue;
                    }
                } else {
                    if (poolableConnection.conn.isClosed()) {
                        discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
                        continue;
                    }
    
                    if (testWhileIdle) {
                        final DruidConnectionHolder holder = poolableConnection.holder;
                        long currentTimeMillis             = System.currentTimeMillis();
                        long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
                        long lastExecTimeMillis            = holder.lastExecTimeMillis;
                        long lastKeepTimeMillis            = holder.lastKeepTimeMillis;
    
                        if (checkExecuteTime
                                && lastExecTimeMillis != lastActiveTimeMillis) {
                            lastActiveTimeMillis = lastExecTimeMillis;
                        }
    
                        if (lastKeepTimeMillis > lastActiveTimeMillis) {
                            lastActiveTimeMillis = lastKeepTimeMillis;
                        }
    
                        long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;
    
                        long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
    
                        if (timeBetweenEvictionRunsMillis <= 0) {
                            timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                        }
    
                        if (idleMillis >= timeBetweenEvictionRunsMillis
                                || idleMillis < 0 // unexcepted branch
                                ) {
                            boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                            if (!validate) {
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("skip not validate connection.");
                                }
    
                                discardConnection(poolableConnection.holder);
                                 continue;
                            }
                        }
                    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    连接池是如何判断连接是否有效的?
    判断连接是否可用同testOnBorrow
    总结
    testWhileIdle的作用跟testOnBorrow是差不多的,都是在获取连接的时候测试连接的有效性,如果两者都为true,则testOnBorrow优先级高,则不会使用到testWhileIdle。

    7 testOnBorrow是什么意思?

    testOnBorrow:如果为true(默认false),当应用向连接池申请连接时,连接池会判断这条连接是否是可用的。
    testOnBorrow什么时候会用到?
    这个参数主要在DruidDataSource的getConnectionDirect方法中用到

    public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
        int notFullTimeoutRetryCnt = 0;
        for (;;) {
            // handle notFullTimeoutRetry
            DruidPooledConnection poolableConnection;
            try {
                poolableConnection = getConnectionInternal(maxWaitMillis);
            } catch (GetConnectionTimeoutException ex) {
                if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
                    notFullTimeoutRetryCnt++;
                    if (LOG.isWarnEnabled()) {
                        LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
                    }
                    continue;
                }
                throw ex;
            }
            //测试即将返回的连接是否可用
            if (testOnBorrow) {
                boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                if (!validate) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("skip not validate connection.");
                    }
    
                    discardConnection(poolableConnection.holder);
                    continue;
                }
            }
        //。。。
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    连接池是如何判断连接是否有效的?

    如果是常用的数据库,则使用${DBNAME}ValidConnectionChecker进行判断,比如Mysql数据库,使用MySqlValidConnectionChecker的isValidConnection进行判断;
    如果是其他数据库,则使用validationQuery判断;
    具体验证规则可查看:Druid配置参数详解-validationQuery

    如果验证不通过怎么办?
    验证不通过则会直接关闭该连接,并重新从连接池获取下一条连接;

       //测试即将返回的连接是否可用
        if (testOnBorrow) {
            boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
            if (!validate) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("skip not validate connection.");
                }
                //验证不通过则直接剔除该连接,并重新获取下一条连接
                discardConnection(poolableConnection.holder);
                continue;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结
    testOnBorrow能够确保我们每次都能获取到可用的连接,但如果设置成true,则每次获取连接的时候都要到数据库验证连接有效性,这在高并发的时候会造成性能下降,可以将testOnBorrow设成false,testWhileIdle设置成true这样能获得比较好的性能。

    8 validationQuery是什么意思?

    validationQuery:Druid用来测试连接是否可用的SQL语句,默认值每种数据库都不相同:
    Mysql:SELECT 1;
    SQLSERVER:SELECT 1;
    ORACLE:SELECT ‘x’ FROM DUAL;
    PostGresql:SELECT ‘x’;

    validationQuery什么时候会起作用?
    当Druid遇到testWhileIdle,testOnBorrow,testOnReturn时,就会验证连接的有效性,验证规则如下:
    如果有相关数据库的ValidConnectionChecker,则使用ValidConnectionChecker验证(Druid提供常用数据库的ValidConnectionChecker,包括,MySqlValidConnectionChecker,OracleValidConnectionChecker,PGValidConnectionChecker);

    如果没有ValidConnectionChecker,则直接使用validationQuery验证;
    ValidConnectionChecker是如何验证的?
    MySqlValidConnectionChecker会使用Mysql独有的ping方式进行验证,其他数据库其实也都是使用validationQuery进行验证
    MySqlValidConnectionChecker验证方式

      public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
            if (conn.isClosed()) {
                return false;
            }
            //Mysql使用com.mysql.jdbc.MySQLConnection的pingInternal方法进行验证
            if (usePingMethod) {
                if (conn instanceof DruidPooledConnection) {
                    conn = ((DruidPooledConnection) conn).getConnection();
                }
    
                if (conn instanceof ConnectionProxy) {
                    conn = ((ConnectionProxy) conn).getRawObject();
                }
    
                if (clazz.isAssignableFrom(conn.getClass())) {
                    if (validationQueryTimeout <= 0) {
                        validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
                    }
    
                    try {
                        ping.invoke(conn, true, validationQueryTimeout * 1000);
                    } catch (InvocationTargetException e) {
                        Throwable cause = e.getCause();
                        if (cause instanceof SQLException) {
                            throw (SQLException) cause;
                        }
                        throw e;
                    }
                    return true;
                }
            }
    
            String query = validateQuery;
            if (validateQuery == null || validateQuery.isEmpty()) {
                query = DEFAULT_VALIDATION_QUERY;
            }
    
            Statement stmt = null;
            ResultSet rs = null;
            try {
                stmt = conn.createStatement();
                if (validationQueryTimeout > 0) {
                    stmt.setQueryTimeout(validationQueryTimeout);
                }
                rs = stmt.executeQuery(query);
                return true;
            } finally {
                JdbcUtils.close(rs);
                JdbcUtils.close(stmt);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    总结
    不同数据库的默认值不同;
    如果是Mysql数据库,则validationQuery不会起作用,Mysql会使用ping的方式验证;

    9 Druid配置参数详解-maxEvictableIdleTimeMillis,minEvictableIdleTimeMillis

    连接池是怎么判断一条连接是Idle状态的?

    就是通过这两个参数进行判断的
    minEvictableIdleTimeMillis:最小空闲时间,默认30分钟,如果连接池中非运行中的连接数大于minIdle,并且那部分连接的非运行时间大于minEvictableIdleTimeMillis,则连接池会将那部分连接设置成Idle状态并关闭;也就是说如果一条连接30分钟都没有使用到,并且这种连接的数量超过了minIdle,则这些连接就会被关闭了。
    maxEvictableIdleTimeMillis:最大空闲时间,默认7小时,如果minIdle设置得比较大,连接池中的空闲连接数一直没有超过minIdle,这时那些空闲连接是不是一直不用关闭?当然不是,如果连接太久没用,数据库也会把它关闭,这时如果连接池不把这条连接关闭,系统就会拿到一条已经被数据库关闭的连接。为了避免这种情况,Druid会判断池中的连接如果非运行时间大于maxEvictableIdleTimeMillis,也会强行把它关闭,而不用判断空闲连接数是否小于minIdle;

    这两参数是怎么起作用的?

    这两参数是在DestroyTask的shrink方法中用来判断连接是不是应该被关闭的。
    //关闭条件,空闲时间大于minEvictableIdleTimeMillis,并且空闲连接大于minIdle,
    // 其中checkCount为poolingCount - minIdle,即可能被关闭的连接数量
    //或者空闲时间大于maxEvictableIdleTimeMillis

    if (idleMillis >= minEvictableIdleTimeMillis) {
         if (checkTime && i < checkCount) {
                  evictConnections[evictCount++] = connection;
                   continue;
         } else if (idleMillis > maxEvictableIdleTimeMillis) {
                    evictConnections[evictCount++] = connection;
                    continue;
           }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结
    minEvictableIdleTimeMillis连接空闲时间大于该值并且池中空闲连接大于minIdle则关闭该连接
    maxEvictableIdleTimeMillis连接空闲时间大于该值,不管minIdle都关闭该连接

    10 Druid配置参数详解-removeAbandoned,logAbandoned,removeAbandonedTimeoutMillis

    removeAbandoned:如果连接泄露,是否需要回收泄露的连接,默认false;
    logAbandoned:如果回收了泄露的连接,是否要打印一条log,默认false;
    removeAbandonedTimeoutMillis:连接回收的超时时间,默认5分钟;

    画外音:这两个参数笔者认为非常重要,但是不知为何,Druid默认是不开启的,并且官方的配置例子中也没有配置这两个参数:Druid官方配置,笔者就因为没有配置这两个参数,曾经吃过大亏。

    removeAbandoned,logAbandoned,removeAbandonedTimeoutMillis有什么用?
    举个栗子:笔者当初做过单个事务使用多数据源的功能,重写了Spring的ConnectionHolder,由于笔者的疏忽,每次从ConnectionHolder中获取连接时都获取到了两条连接,但是只是使用了其中的一条,相当于另一条连接只是从连接池中拿出来了,但是再也不会还回去了,这样就导致了连接池中的连接很快就消耗光了,即activeCount=maxActive。

    如果当时我设置了removeAbandoned就不会出现这个问题,应该Druid会定期检查池中借出去的连接是否处于运行状态,如果不是处于运行状态,并且借出时间超过removeAbandonedTimeoutMillis(默认5分钟)就会回收该连接。

    连接是怎么回收的?
    Druid每隔timeBetweenEvictionRunsMillis(默认1分钟)会调用DestroyTask,在这里会判断是否可以回收泄露的连接

    public class DestroyTask implements Runnable {
        public DestroyTask() {
    
        }
    
        @Override
        public void run() {
            shrink(true, keepAlive);
            //判断removeAbandoned是否为true,默认是false,不知为何
            if (isRemoveAbandoned()) {
                removeAbandoned();
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    for (; iter.hasNext();) {
        DruidPooledConnection pooledConnection = iter.next();
        
        //判断该连接是否还在运行,只回收不运行的连接
        //Druid会在连接执行query,update的时候设置为正在运行,
        // 并在回收后设置为不运行
        if (pooledConnection.isRunning()) {
            continue;
        }
    
        long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
        
        //判断连接借出去的时间大小
        if (timeMillis >= removeAbandonedTimeoutMillis) {
            iter.remove();
            pooledConnection.setTraceEnable(false);
            abandonedList.add(pooledConnection);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
      //判断是否要记录连接回收日志,这个很重要,可以及时发现项目中是否有连接泄露
        if (isLogAbandoned()) {
            StringBuilder buf = new StringBuilder();
            buf.append("abandon connection, owner thread: ");
            buf.append(pooledConnection.getOwnerThread().getName());
            buf.append(", connected at : ");
            buf.append(pooledConnection.getConnectedTimeMillis());
            buf.append(", open stackTrace\n");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结
    笔者认为这三个参数非常重要,但是官方默认是不开启的,建议使用Druid的项目都设置这三个参数,这样可以有效的发现代码中是否有连接泄露。

    11 testOnReturn是什么意思?

    testOnReturn:如果为true(默认false),当应用使用完连接,连接池回收连接的时候会判断该连接是否还可用。
    testOnReturn什么时候会用到?
    当连接使用完,调用commit或者rollback方法后,连接池会回收该连接,该参数主要在DruidDataSource的recycle方法中用到

    if (testOnReturn) {
        boolean validate = testConnectionInternal(holder, physicalConnection);
        if (!validate) {
            JdbcUtils.close(physicalConnection);
    
            destroyCountUpdater.incrementAndGet(this);
    
            lock.lock();
            try {
                activeCount--;
                closeCount++;
            } finally {
                lock.unlock();
            }
            return;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    连接池是如何判断连接是否有效的?
    判断连接是否可用同testOnBorrow
    Druid配置参数详解-testOnBorrow

  • 相关阅读:
    携创教育:成人高考流程&专业推荐&常见问题答疑
    Java Spring MVC如何编写一个示例程序呢?
    【FPGA教程案例59】深度学习案例6——基于FPGA的CNN卷积神经网络之整体实现
    Linux进程信号
    python requests 配置重试次数
    基于Android Studio+Android SDK的手机通讯录管理软件设计
    异步回调
    软件测试个人求职简历该怎么写,模板在这里
    自定义一个注解,用它标记的执行并于给定值做对比(即简单的junit)
    YoloV:视频中目标实时检测依然很棒(附源代码下载)
  • 原文地址:https://blog.csdn.net/chuige2013/article/details/128049889