DBCP2的核心參數配置

1、核心的包

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
  <version>${commons.pool.version}</version>
</dependency>

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-dbcp2</artifactId>
  <version>${commons.dbcp2.version}</version>
</dependency>

2、基本內容介紹

(1)dbcp藉助於commons-pool這個jar包,commons-pool是一種對象池的實現,下面給出一些基本的pool中的概念。

對象生命週期狀態枚舉

public enum PooledObjectState {
    // 空閒的對象
    IDLE,
    // 使用中的對象
    ALLOCATED,
    // 正在被驅逐線程測試的對象
    EVICTION,
    // 正在被驅逐線程測試,一單測試完成後,會將
    EVICTION_RETURN_TO_HEAD,
    // 正在被驗證的對象
    VALIDATION,
    // 正在被驗證的對象,一單驗證通過,會直接通過borrow借出。
    VALIDATION_PREALLOCATED,
    // 驅逐線程驗證的對象,沒問題回將對象放到隊列的頭部
    VALIDATION_RETURN_TO_HEAD,
    // 不合法的對象,之後會被移出
    INVALID,
    //廢棄的對象,之後會被銷燬
    ABANDONED,
    //正在返回到池中的對象
    RETURNING
}

3、核心參數和代碼中的體現

  • initialSize

當連接池創建時初始化創建連接的數目

當首次獲取數據庫連接時,這個參數起到作用,會初始化initialSize這個數目的空閒連接數。在BasicDataSource的
createDataSource這個方法中,會有如下代碼,進行初始化連接的建立。

// If initialSize > 0, preload the pool
try {
    for (int i = 0; i < initialSize; i++) {
        connectionPool.addObject();
    }
} catch (final Exception e) {
    closeConnectionPool();
    throw new SQLException("Error preloading the connection pool", e);
}
  • minIdle

最小化的空閒連接數目,當空閒驅逐線程進行超時空閒線程驅逐時,驅逐完畢後能夠保證空閒連接數目至少達到最小值,這個參數必須在設置timeBetweenEvictionRunsMillis這個參數爲正值時纔會起作用(就是讓驅逐線程運行起來)

在BaseGenericObjectPool.Evictor中,會用到minIdle,Evictor實現了Runnable接口,驅逐線程運行的就是這個實例任務。當完成了
超時連接和基於其他參數的連接後,會調用內部方法ensureMinIdle()保證空閒連接滿足minIdle這個值。

private void ensureIdle(final int idleCount, final boolean always) throws Exception {
    if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
        return;
    }

    // 空閒連接小於設置的minIdle
    while (idleObjects.size() < idleCount) {
        final PooledObject<T> p = create();
        if (p == null) {
            // Can't create objects, no reason to think another call to
            // create will work. Give up.
            break;
        }
        if (getLifo()) {
            idleObjects.addFirst(p);
        } else {
            idleObjects.addLast(p);
        }
    }
    if (isClosed()) {
        // Pool closed while object was being added to idle objects.
        // Make sure the returned object is destroyed rather than left
        // in the idle object pool (which would effectively be a leak)
        clear();
    }
}
  • maxIdle

最大的空閒連接的數目,當用戶用完一個連接後,然後重新把這個連接放到空閒連接池中,如果此時的空閒連接數超過maxIdle,就會destroy部分
多餘的空閒線程。

  • maxWaitMillis

獲取一個連接的最長超時時間,如果等於小於等於0表示無限等待。

  • validationQuery

當獲取一個連接後,用這個sql驗證連接的有效性。

  • testOnBorrow

當從池子中獲取一個連接時,是否需要驗證連接的有效性,如果需要驗證,就使用validationQuery設置的語句驗證連接的有效性。

  • removeAbandonedTimeout

一個活動連接的超時時間(意思就是一個連接在removeAbandonedTimeout這個時間裏一直處於使用狀態),一個連接在過長的時間裏處於活動狀態,要麼是連接設置狀態出現問題,要麼是執行一個很慢的sql語句,此時主動回收,可以中斷慢sql的執行。這個參數只有removeAbandonedOnBorrow
和removeAbandonedOnMaintenance這個參數其中之一設置爲true時才能起作用。

  • removeAbandonedOnBorrow

表示當在pool中拿連接時,嘗試將廢棄的連接移除掉。

  • minEvictableIdleTimeMillis

表示一個連接持續minEvictableIdleTimeMillis這麼長的時間一直處於空閒時,空閒連接會嘗試驅逐。注:主要可以防止一個連接空閒時間太久了,服務端主要將連接斷開了,導致連接時效,從而查庫失敗。

  • timeBetweenEvictionRunsMillis

表示驅逐線程每隔多久運行一次。

只有這個參數設置的值大於0,纔會啓動驅逐線程

protected void startPoolMaintenance() {
  // 只有timeBetweenEvictionRunsMillis這個參數大於0,纔會啓動驅逐線程
    if (connectionPool != null && timeBetweenEvictionRunsMillis > 0) {
        connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    }
}

public final void setTimeBetweenEvictionRunsMillis(
            final long timeBetweenEvictionRunsMillis) {
    this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    startEvictor(timeBetweenEvictionRunsMillis);
}

//啓動驅逐線程
final void startEvictor(final long delay) {
        synchronized (evictionLock) {
      EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS);
      evictor = null;
      evictionIterator = null;
      if (delay > 0) {
          evictor = new Evictor();
          EvictionTimer.schedule(evictor, delay, delay);
      }
  }
}
  • testWhileIdle

表示驅逐線程是否要驗證連接的有效性,就算一個空閒連接沒有超時,但是如果有效性無法驗證通過,也會進行驅逐。(驗證有效性,主要是防止一個空閒連接服務端在很短的時間裏就斷開了。)

testWhileIdle參數也是針對於驅逐線程,當使用驅逐線程判斷線程沒有超時不需要驅逐時,會判斷testWhileIdle是否爲true,如果是,
對連接進行校驗,校驗失敗也會從連接池中移除此線程。

核心代碼如下:

public void evict() throws Exception {
    assertOpen();
    // 有空閒連接就進行校驗
    if (idleObjects.size() > 0) {

        PooledObject<T> underTest = null;
        final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();

        synchronized (evictionLock) {
            final EvictionConfig evictionConfig = new EvictionConfig(
                    getMinEvictableIdleTimeMillis(),
                    getSoftMinEvictableIdleTimeMillis(),
                    getMinIdle());

            final boolean testWhileIdle = getTestWhileIdle();

            for (int i = 0, m = getNumTests(); i < m; i++) {
                if (evictionIterator == null || !evictionIterator.hasNext()) {
                    evictionIterator = new EvictionIterator(idleObjects);
                }
                if (!evictionIterator.hasNext()) {
                    // Pool exhausted, nothing to do here
                    return;
                }

                try {
                    underTest = evictionIterator.next();
                } catch (final NoSuchElementException nsee) {
                    // Object was borrowed in another thread
                    // Don't count this as an eviction test so reduce i;
                    i--;
                    evictionIterator = null;
                    continue;
                }

                if (!underTest.startEvictionTest()) {
                    // Object was borrowed in another thread
                    // Don't count this as an eviction test so reduce i;
                    i--;
                    continue;
                }

                // User provided eviction policy could throw all sorts of
                // crazy exceptions. Protect against such an exception
                // killing the eviction thread.
                boolean evict;
                try {
                    // 根據配置的驅逐策略進行驅逐校驗
                    evict = evictionPolicy.evict(evictionConfig, underTest,
                            idleObjects.size());
                } catch (final Throwable t) {
                    // Slightly convoluted as SwallowedExceptionListener
                    // uses Exception rather than Throwable
                    PoolUtils.checkRethrow(t);
                    swallowException(new Exception(t));
                    // Don't evict on error conditions
                    evict = false;
                }
                // 如果應該驅逐,直接銷燬連接
                if (evict) {
                    destroy(underTest);
                    destroyedByEvictorCount.incrementAndGet();
                } else {
                    // 判斷testWhileIdle是否爲true,進行校驗。
                    if (testWhileIdle) {
                        boolean active = false;
                        try {
                            factory.activateObject(underTest);
                            active = true;
                        } catch (final Exception e) {
                            destroy(underTest);
                            destroyedByEvictorCount.incrementAndGet();
                        }
                        if (active) {
                            if (!factory.validateObject(underTest)) {
                                destroy(underTest);
                                destroyedByEvictorCount.incrementAndGet();
                            } else {
                                try {
                                    factory.passivateObject(underTest);
                                } catch (final Exception e) {
                                    destroy(underTest);
                                    destroyedByEvictorCount.incrementAndGet();
                                }
                            }
                        }
                    }
                    if (!underTest.endEvictionTest(idleObjects)) {
                        // TODO - May need to add code here once additional
                        // states are used
                    }
                }
            }
        }
    }
    final AbandonedConfig ac = this.abandonedConfig;
    // 如果removeAbandonedOnMaintenance設置爲true時,在驅逐線程中還會移除廢棄的連接。
    if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
        removeAbandoned(ac);
    }
}
  • numTestsPerEvictionRun

表示驅逐線程每次校驗的連接的數目

  • maxTotal

表示連接的最大數目,包括空閒連接和活動的連接。

GenericObjectPool類中有兩個對象allObjects和idleObjects,其中allObjects就是連接對象的總集合,
idleObjects就是空閒連接。

  • accessToUnderlyingConnectionAllowed

主要是控制PoolGuard是否允許訪問正在處理中的線程

  • connectionProperties

設置連接的一些屬性參數,主要是驅動器進行數據庫連接時需要用到的一些參數。

  • connectionInitSqls

當一個連接物理連接創建後,執行的一些sql語句的集合。

調用PoolableConnectionFactory的makObject方法構建連接時,會執行連接創建的初始化語句。

protected void initializeConnection(final Connection conn) throws SQLException {
  final Collection<String> sqls = connectionInitSqls;
  if (conn.isClosed()) {
      throw new SQLException("initializeConnection: connection closed");
  }
  if (null != sqls) {
      try (Statement stmt = conn.createStatement()) {
          for (final String sql : sqls) {
              Objects.requireNonNull(sql, "null connectionInitSqls element");
              stmt.execute(sql);
          }
      }
  }
}
  • testOnReturn

當一個連接重新歸還到池子中時,校驗連接的有效性。

當一個連接使用完後重新往連接池中放時,如果testOnReturn設置爲true,則校驗連接,如何正常,會重新放入連接池,否則
銷燬連接。部分歸還連接的代碼如下:

/**
 * Return me to my pool.
 */
@Override
public void close() throws SQLException {
    // calling close twice should have no effect
    if (!isClosed()) {
        try {
           // 整理就是歸還連接池
            pool.returnObject(key, this);
        } catch (final SQLException e) {
            throw e;
        } catch (final RuntimeException e) {
            throw e;
        } catch (final Exception e) {
            throw new SQLException("Cannot close preparedstatement (return to pool failed)", e);
        }
    }
}
  • validationQueryTimeoutSeconds

執行一個校驗連接查詢的超時時間,當小於等於0時,沒有超時的限制。

public void validateConnection(final PoolableConnection conn) throws SQLException {
    if (conn.isClosed()) {
        throw new SQLException("validateConnection: connection closed");
    }
    conn.validate(validationQuery, validationQueryTimeoutSeconds);
}
  • removeAbandonedOnMaintenance

當對pool進行檢查時,移除超時的廢棄連接。也是在驅逐線程的evict()方法中用到這個參數

  • evictionPolicyClassName

定義驅逐策略的類,也就是一個線程是否應該進行驅逐可以自己定義一個EvictionPolicy這個接口的實現類。

  • fastFailValidation

使用工廠創建的連接能夠進行快速失敗驗證, 驗證時直接拋出異常

  • testOnCreate

表示一個對象創建完成後,直接進行校驗。

  • softMinEvictableIdleTimeMillis

和minEvictableIdleTimeMillis類似,只是softMinEvictableIdleTimeMillis這個參數需要保證池中至少有minIdle個空閒連接存在才進行驅逐。

4、使用實例

public static void main(String[] args) throws Exception {
    testGetConnection();
}

public static void testGetConnection() throws Exception {
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setInitialSize(2);
    dataSource.setMinIdle(3);
    dataSource.setMaxIdle(6);
//        dataSource.setMaxWaitMillis(500);
    dataSource.setMaxTotal(10);
    dataSource.setAccessToUnderlyingConnectionAllowed(true);
    dataSource.setConnectionProperties("m=1");
    List<String> sqls = new ArrayList<>();
    sqls.add("select 1");
    dataSource.setConnectionInitSqls(sqls);
    dataSource.setTestOnReturn(true);
//        dataSource.setValidationQueryTimeout(1);
    dataSource.setRemoveAbandonedOnMaintenance(true);
    // dataSource.setEvictionPolicyClassName();
    dataSource.setFastFailValidation(true);
    dataSource.setTestOnCreate(true);
//        dataSource.setSoftMinEvictableIdleTimeMillis(100);
    dataSource.setValidationQuery("select 1");
    dataSource.setTestOnBorrow(true);
    dataSource.setRemoveAbandonedTimeout(100);
    dataSource.setRemoveAbandonedOnBorrow(true);
    dataSource.setMinEvictableIdleTimeMillis(3000);
    dataSource.setTimeBetweenEvictionRunsMillis(3000);
    dataSource.setTestWhileIdle(true);
    dataSource.setNumTestsPerEvictionRun(10);
    dataSource.setUrl("jdbc:mysql://localhost:3306/testdb0");
    dataSource.setPassword("jcw123");
    dataSource.setUsername("root");
    for(int i = 0; i < 5; i++) {
        new Thread(()->{
            while(true) {
                try {
                Connection connection = dataSource.getConnection();
                PreparedStatement preparedStatement = connection.prepareStatement("select * from testtable0 limit 1");
                ResultSet resultSet = preparedStatement.executeQuery();
                resultSet.next();
                System.out.println(resultSet.getInt(2));

                    Thread.sleep(1000);
                }catch (Exception e) {}
            }
        }, "tread" + i).start();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章