Druid配置參數(shù)詳解-testOnBorrow
Druid是一個由阿里開源的數(shù)據(jù)庫連接池,Druid的配置非常豐富,但是設(shè)置不當會對生產(chǎn)環(huán)境造成嚴重影響,網(wǎng)上Druid的資料雖多,但大部分都是互相復(fù)制粘貼,有很多不準確甚至完全錯誤的描述,Druid已經(jīng)開源很久,而且作者WenShao的工作重心也已經(jīng)不在Druid上,有些功能估計他自己都不太了解了。本系列將從源代碼的角度分析Druid目前的最新版本(1.1.21)各個常用的配置項的具體含義以及是怎么起作用的。
畫外音:目前Druid在開源中國舉辦的2019年度最受歡迎中國開源軟件中排名第7名,支持Druid的朋友可以去投票哇。2019年度最受歡迎中國開源軟件
testOnBorrow是什么意思?
testOnBorrow:如果為true(默認false),當應(yīng)用向連接池申請連接時,連接池會判斷這條連接是否是可用的。
testOnBorrow什么時候會用到?
這個參數(shù)主要在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;
}
}
//。。。
}
連接池是如何判斷連接是否有效的?
- 如果是常用的數(shù)據(jù)庫,則使用${DBNAME}ValidConnectionChecker進行判斷,比如Mysql數(shù)據(jù)庫,使用MySqlValidConnectionChecker的isValidConnection進行判斷;
- 如果是其他數(shù)據(jù)庫,則使用validationQuery判斷;
具體驗證規(guī)則可查看:Druid配置參數(shù)詳解-validationQuery
protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {
String sqlFile = JdbcSqlStat.getContextSqlFile();
String sqlName = JdbcSqlStat.getContextSqlName();
if (sqlFile != null) {
JdbcSqlStat.setContextSqlFile(null);
}
if (sqlName != null) {
JdbcSqlStat.setContextSqlName(null);
}
try {//如果是常用的數(shù)據(jù)庫,則使用相關(guān)數(shù)據(jù)庫的validConnectionChecker進行驗證,比如MysqlValidConnectionChecker,否則使用validationQuery驗證
if (validConnectionChecker != null) {
boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
long currentTimeMillis = System.currentTimeMillis();
if (holder != null) {
holder.lastValidTimeMillis = currentTimeMillis;
holder.lastExecTimeMillis = currentTimeMillis;
}
if (valid && isMySql) { // unexcepted branch
long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
if (lastPacketReceivedTimeMs > 0) {
long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;
if (lastPacketReceivedTimeMs > 0 //
&& mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
discardConnection(holder);
String errorMsg = "discard long time none received connection. "
+ ", jdbcUrl : " + jdbcUrl
+ ", jdbcUrl : " + jdbcUrl
+ ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;
LOG.error(errorMsg);
return false;
}
}
}
if (valid && onFatalError) {
lock.lock();
try {
if (onFatalError) {
onFatalError = false;
}
} finally {
lock.unlock();
}
}
return valid;
}
if (conn.isClosed()) {
return false;
}
if (null == validationQuery) {
return true;
}
Statement stmt = null;
ResultSet rset = null;
try {
stmt = conn.createStatement();
if (getValidationQueryTimeout() > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
rset = stmt.executeQuery(validationQuery);
if (!rset.next()) {
return false;
}
} finally {
JdbcUtils.close(rset);
JdbcUtils.close(stmt);
}
if (onFatalError) {
lock.lock();
try {
if (onFatalError) {
onFatalError = false;
}
} finally {
lock.unlock();
}
}
return true;
} catch (Throwable ex) {
// skip
return false;
} finally {
if (sqlFile != null) {
JdbcSqlStat.setContextSqlFile(sqlFile);
}
if (sqlName != null) {
JdbcSqlStat.setContextSqlName(sqlName);
}
}
}
public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
if (conn.isClosed()) {
return false;
}
//如果數(shù)據(jù)庫驅(qū)動有pingInternal方法,則使用pingInternal方法判斷,否則使用validateQuery進行判斷
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);
}
}
如果驗證不通過怎么辦?
驗證不通過則會直接關(guān)閉該連接,并重新從連接池獲取下一條連接;
//測試即將返回的連接是否可用
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
//驗證不通過則直接剔除該連接,并重新獲取下一條連接
discardConnection(poolableConnection.holder);
continue;
}
}
總結(jié)
testOnBorrow能夠確保我們每次都能獲取到可用的連接,但如果設(shè)置成true,則每次獲取連接的時候都要到數(shù)據(jù)庫驗證連接有效性,這在高并發(fā)的時候會造成性能下降,可以將testOnBorrow設(shè)成false,testWhileIdle設(shè)置成true這樣能獲得比較好的性能。