Druid中的maxIdle爲什麼是沒用的?springboot druid 數據庫連接池連接失敗後一直重連

常見問題 · alibaba/druid Wiki (github.com)

initialSize是什麼意思?

initialSize:連接池初始化時初始化的數據庫連接數

initialSize在哪個階段會起作用?

當項目第一次進行增,刪,改,查的時候,連接池會初始化,這個時候會根據initialSize參數初始化數據庫連接放入連接池中。

畫外音:這就是爲什麼第一次進行數據庫操作的時候,響應會比較慢的原因,創建數據庫連接是很耗時的,所以初始化連接並不是越多越好

initialSize是怎麼起作用的?

當連接池初始化時,會調用DruidDataSource的init初始化數據庫連接

總結

initialSize的作用是告訴連接池初始化時應該初始化的物理連接數,要注意的是這個值越大,第一次調用數據庫時越慢。

 

druid 參數設置

1)Max-active:指的是連接池裏允許的最大活躍連接數,這個值根據應用實際情況調整。
2)Min-idle:關掉多餘連接,保留有效連接,節省數據庫的資源,這個值根據應用實際情況調整。
3)Max-wait,指應用線程等待連接的超時。可以配幾秒範圍,根據業務應用實際情況進行判定。
4)Validation-query,指的是連接池探測當前連接是否是健康的SQL語句。如果是較新的JDBC,不會發SQL語句,而是發Ping命令。
5)Validation-query-timeout,指的就是探測超時的時間。
6)Test-on-borrow指連接從連接池裏取出時,連接池是否需要對連接進行健康探測。建議關閉False。
7)Test-on-return,建議關閉False。
8)Test-while-idle,指的是控制當連接處於空閒狀態時,是否需檢測連接的健康狀態。建議打開True。
9)Time-between-eviction-runs-millis指的是觸發空閒連接健康探測閾值,需要跟上面的Test-while結合起來。
10)Remove-abandoned,泄露連接強制回收,默認是False關閉。
11)Remove-abandoned-timeout,指的是強制回收的觸發時間閾值。配置時間不要太短,因爲業務長時間使用連接,所以超時時間要比業務實際合理時間要高。配置參數單位是“秒”。
12)Log-abandoned,指的是關閉被泄露連接時輸出堆棧。當一個連接被探測爲連接泄露且強制關閉的時候,是否要

下面配置不建議mysql環境配置:

spring.datasource.druid.max-open-prepared-statements=20
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
spring.datasource.druid.pool-prepared-statements=false  //mysql環境設置成 false

使用 Remove-abandoned 檢測線程池泄露

配置:


# 檢測線程池超時回收
spring.datasource.druid.remove-abandoned=true
spring.datasource.druid.remove-abandoned-timeout=5
spring.datasource.druid.log-abandoned=true

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.connect-properties.config.decrypt=false
spring.datasource.druid.connect-properties.druid.stat.logSlowSql=true
spring.datasource.druid.connect-properties.druid.stat.slowSqlMillis=200
spring.datasource.druid.filters=config,wall,stat
spring.datasource.druid.initial-size=1
spring.datasource.druid.max-active=3
spring.datasource.druid.max-wait=300
spring.datasource.druid.min-evictable-idle-time-millis=30000
spring.datasource.druid.min-idle=1
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-password=admin
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.validation-query=select 1
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.exclusions=/druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.session-stat-max-count=10
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://192.168.xxx.xx:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai

設置從連接池 拿取連接不超過 300毫秒

測試:

com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 301, active 3, maxActive 3, creating 0, runningSqlCount 2 : select * from t_user t1 order by rand() limit 1
com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 302, active 3, maxActive 3, creating 0, runningSqlCount 3 : select * from t_user t1 order by rand() limit 1
com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 300, active 3, maxActive 3, creating 0, runningSqlCount 3 : select * from t_user t1 order by rand() limit 1
com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 301, active 3, maxActive 3, creating 0, runningSqlCount 3 : select * from t_user t1 order by rand() limit 1

就可以查看到底是哪個sql引起泄露的了

springboot druid 數據庫連接池連接失敗後一直重連_小碼code的博客-CSDN博客

在使用個人阿里雲測試機,在查詢實時輸出日誌時,看到數據庫連接失敗後,服務器一直在重連服務器。開始以爲是遭受重複攻擊,後面把服務重啓後,就沒有出現一直重連的情況。看以下輸出日誌:

2022-02-09 11:04:58.896 ERROR 16876 --- [eate-1550991149] com.alibaba.druid.pool.DruidDataSource   : create connection SQLException, url: jdbc:mysql://47.98.67,98:1234/test?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC, errorCode 1045, state 28000

java.sql.SQLException: Access denied for user 'root'@'113.90.123.76' (using password: YES)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:455) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:199) ~[mysql-connector-java-8.0.16.jar:8.0.16]
    at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:156) ~[druid-1.1.10.jar:1.1.10]
    at com.alibaba.druid.filter.stat.StatFilter.connection_connect(StatFilter.java:218) ~[druid-1.1.10.jar:1.1.10]
    at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:150) ~[druid-1.1.10.jar:1.1.10]
    at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1560) ~[druid-1.1.10.jar:1.1.10]
    at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1623) ~[druid-1.1.10.jar:1.1.10]
    at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2468) ~[druid-1.1.10.jar:1.1.10]

注意上面一直有 druid 數據庫連接池的提示,這裏就想到可能是 druid 連接池的問題,然後去掉 druid maven 依賴後在請求接口就不會出現重連的問題。

druid 重連原因

在上圖源碼找到最後一行 DruidDataSource.java:2468 定位到源碼上,CreateConnectionThread 創建連接線程,看一下 CreateConnectionThread 源碼:

public class CreateConnectionThread extends Thread {

        public CreateConnectionThread(String name){
            super(name);
            this.setDaemon(true);
        }

        public void run() {
            initedLatch.countDown();

            long lastDiscardCount = 0;
            int errorCount = 0;
            for (;;) {
                // addLast
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e2) {
                    break;
                }

                long discardCount = DruidDataSource.this.discardCount;
                boolean discardChanged = discardCount - lastDiscardCount > 0;
                lastDiscardCount = discardCount;

                try {
                    boolean emptyWait = true;

                    if (createError != null
                            && poolingCount == 0
                            && !discardChanged) {
                        emptyWait = false;
                    }

                    if (emptyWait
                            && asyncInit && createCount < initialSize) {
                        emptyWait = false;
                    }

                    if (emptyWait) {
                        // 必須存在線程等待,才創建連接
                        if (poolingCount >= notEmptyWaitThreadCount //
                                && !(keepAlive && activeCount + poolingCount < minIdle)) {
                            empty.await();
                        }

                        // 防止創建超過maxActive數量的連接
                        if (activeCount + poolingCount >= maxActive) {
                            empty.await();
                            continue;
                        }
                    }

                } catch (InterruptedException e) {
                    lastCreateError = e;
                    lastErrorTimeMillis = System.currentTimeMillis();

                    if (!closing) {
                        LOG.error("create connection Thread Interrupted, url: " + jdbcUrl, e);
                    }
                    break;
                } finally {
                    lock.unlock();
                }

                PhysicalConnectionInfo connection = null;

                try {
                    connection = createPhysicalConnection();
                    setFailContinuous(false);
                } catch (SQLException e) {
                    LOG.error("create connection SQLException, url: " + jdbcUrl + ", errorCode " + e.getErrorCode()
                              + ", state " + e.getSQLState(), e);

                    errorCount++;
                    if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
                        // fail over retry attempts
                        setFailContinuous(true);
                        if (failFast) {
                            lock.lock();
                            try {
                                notEmpty.signalAll();
                            } finally {
                                lock.unlock();
                            }
                        }

                        if (breakAfterAcquireFailure) {
                            break;
                        }

                        try {
                            Thread.sleep(timeBetweenConnectErrorMillis);
                        } catch (InterruptedException interruptEx) {
                            break;
                        }
                    }
                } catch (RuntimeException e) {
                    LOG.error("create connection RuntimeException", e);
                    setFailContinuous(true);
                    continue;
                } catch (Error e) {
                    LOG.error("create connection Error", e);
                    setFailContinuous(true);
                    break;
                }

                if (connection == null) {
                    continue;
                }

                boolean result = put(connection);
                if (!result) {
                    JdbcUtils.close(connection.getPhysicalConnection());
                    LOG.info("put physical connection to pool failed.");
                }

                errorCount = 0; // reset errorCount
            }
        }
    }

這是一個多線程的類,而 run 方法裏面設置了沒有限制的 for 循環 for (;;) {}, 而日誌報錯定位的信息:

connection = createPhysicalConnection();

如果符合條件 errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0 會再次嘗試重連,先看一下這幾個參數的含義:

errorCount 錯誤次數

在 run 方法初始化時爲零,每次連接失敗,會自動加1

connectionErrorRetryAttempts

連接錯誤重試次數,默認值爲 1。

protected int  connectionErrorRetryAttempts  = 1;

timeBetweenConnectErrorMillis

連接間隔時間,單位毫秒。默認值爲 500。

protected volatile long timeBetweenConnectErrorMillis = DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS;
public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500;

我們在連接數據庫失敗後,要不在裏面 break 中斷,其中有

if (breakAfterAcquireFailure) {
     break;
}

將改 break-after-acquire-failure 設置成 true,在 application.properties 文件如下配置:

spring.datasource.druid.break-after-acquire-failure=true

如果想多嘗試連接幾次,需要設置 connection-error-retry-attempts ,當 errorCount 大於 connectionErrorRetryAttempts 纔會進入到 條件內,纔會中斷循環。在 application.properties 文件如下配置:

spring.datasource.druid.connection-error-retry-attempts=3

總結

druid 數據庫連接失敗,是因爲在使用多線程連接數據時使用了無限制循環連接,需要在連接失敗中斷連接,需要設置 break-after-acquire-failure 爲 true。設置之後數據庫連接不成功也不會不斷的重試。如果要設置重連次數要設置 connection-error-retry-attempts。 如果覺得文章對你有幫助的話,請點個贊吧!

DruidDataSource配置 - DruidDataSource配置屬性列表 - 《Alibaba Druid v1.0 使用手冊》 - 書棧網 · BookStack

DruidDataSource配置兼容DBCP,但個別配置的語意有所區別。

配置 缺省值 說明
name   配置這個屬性的意義在於,如果存在多個數據源,監控的時候可以通過名字來區分開來。如果沒有配置,將會生成一個名字,格式是:"DataSource-" + System.identityHashCode(this). 另外配置此屬性至少在1.0.5版本中是不起作用的,強行設置name會出錯。詳情-點此處
url   連接數據庫的url,不同數據庫不一樣。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username   連接數據庫的用戶名
password   連接數據庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。詳細看這裏
driverClassName 根據url自動識別 這一項可配可不配,如果不配置druid會根據url自動識別dbType,然後選擇相應的driverClassName
initialSize 0 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時
maxActive 8 最大連接池數量
maxIdle 8 已經不再使用,配置了也沒效果
minIdle   最小連接池數量
maxWait   獲取連接時最大等待時間,單位毫秒。配置了maxWait之後,缺省啓用公平鎖,併發效率會有所下降,如果需要可以通過配置useUnfairLock屬性爲true使用非公平鎖。
poolPreparedStatements false 是否緩存preparedStatement,也就是PSCache。PSCache對支持遊標的數據庫性能提升巨大,比如說oracle。在mysql下建議關閉。
maxPoolPreparedStatementPerConnectionSize -1 要啓用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改爲true。在Druid中,不會存在Oracle下PSCache佔用內存過多的問題,可以把這個數值配置大一些,比如說100
validationQuery   用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x'。如果validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
validationQueryTimeout   單位:秒,檢測連接是否有效的超時時間。底層調用jdbc Statement對象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testOnReturn false 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testWhileIdle false 建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
keepAlive false(1.0.28) 連接池中的minIdle數量以內的連接,空閒時間超過minEvictableIdleTimeMillis,則會執行keepAlive操作。
timeBetweenEvictionRunsMillis 1分鐘(1.0.14) 有兩個含義:1) Destroy線程會檢測連接的間隔時間,如果連接空閒時間大於等於minEvictableIdleTimeMillis則關閉物理連接。2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
numTestsPerEvictionRun 30分鐘(1.0.14) 不再使用,一個DruidDataSource只支持一個EvictionRun
minEvictableIdleTimeMillis   連接保持空閒而不被驅逐的最小時間
connectionInitSqls   物理連接初始化的時候執行的sql
exceptionSorter 根據dbType自動識別 當數據庫拋出一些不可恢復的異常時,拋棄連接
filters   屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wall
proxyFilters   類型是List<com.alibaba.druid.filter.Filter>,如果同時配置了filters和proxyFilters,是組合關係,並非替換關係

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章