Druid配置參數詳解-testOnBorrow
Druid是一個由阿里開源的數據庫連接池粱侣,Druid的配置非常豐富捉片,但是設置不當會對生產環(huán)境造成嚴重影響韩容,網上Druid的資料雖多,但大部分都是互相復制粘貼藕坯,有很多不準確甚至完全錯誤的描述团南,Druid已經開源很久,而且作者WenShao的工作重心也已經不在Druid上炼彪,有些功能估計他自己都不太了解了吐根。本系列將從源代碼的角度分析Druid目前的最新版本(1.1.21)各個常用的配置項的具體含義以及是怎么起作用的。
畫外音:目前Druid在開源中國舉辦的2019年度最受歡迎中國開源軟件中排名第7名辐马,支持Druid的朋友可以去投票哇拷橘。2019年度最受歡迎中國開源軟件
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;
}
}
//术幔。。轿塔。
}
連接池是如何判斷連接是否有效的特愿?
- 如果是常用的數據庫,則使用${DBNAME}ValidConnectionChecker進行判斷勾缭,比如Mysql數據庫揍障,使用MySqlValidConnectionChecker的isValidConnection進行判斷;
- 如果是其他數據庫俩由,則使用validationQuery判斷毒嫡;
具體驗證規(guī)則可查看:Druid配置參數詳解-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 {//如果是常用的數據庫,則使用相關數據庫的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;
}
//如果數據庫驅動有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);
}
}
如果驗證不通過怎么辦咬摇?
驗證不通過則會直接關閉該連接伐蒂,并重新從連接池獲取下一條連接;
//測試即將返回的連接是否可用
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
//驗證不通過則直接剔除該連接肛鹏,并重新獲取下一條連接
discardConnection(poolableConnection.holder);
continue;
}
}
總結
testOnBorrow能夠確保我們每次都能獲取到可用的連接逸邦,但如果設置成true,則每次獲取連接的時候都要到數據庫驗證連接有效性在扰,這在高并發(fā)的時候會造成性能下降缕减,可以將testOnBorrow設成false,testWhileIdle設置成true這樣能獲得比較好的性能芒珠。