常見問題 · 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,是組合關係,並非替換關係 |