(1)本文主要是使用Druid作爲Presto的連接池,所遇到的問題,以及的錯誤和解決方法
先看一下我的Druid的配置
- druidDataSource.setTestWhileIdle(true);
- druidDataSource.setTestOnBorrow(true);
- druidDataSource.setTestOnReturn(false);
- druidDataSource.setQueryTimeout(15);
- druidDataSource.setMaxActive(20); 配置Druid的連接數的峯值
- druidDataSource.setMaxWait(6000); 從連接池獲取連接的超時等待時間,單位毫秒
- druidDataSource.setMinIdle(1); 設置池子中的固定的空閒連接數
- druidDataSource.setKeepAlive(true);
- druidDataSource.setTimeBetweenEvictionRunsMillis(30000);Destroy線程會檢測連接的間隔時間,如果連接空閒時間大於等於minEvictableIdleTimeMillis則關閉物理連接。2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
- druidDataSource.setMinEvictableIdleTimeMillis(60000);//配置一個連接在池中最小生存的時間,單位是毫秒;即最多允許一個連接空閒多長時間,超出此時間的連接,會被destroy線程關閉
druidDataSource.setMaxEvictableIdleTimeMillis(90000);配置一個連接在池子中的最大存時間
druidDataSource.setRemoveAbandoned(true);是否強制關閉連接時長大於removeAbandonedTimeoutMillis的連接
druidDataSource.setRemoveAbandonedTimeout(180); 這個是強制關閉的時間值限定單位是秒
//這裏我開啓了Druid的超時回收連接機制
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(5);單位秒
druidDataSource.setLogAbandoned(true); 這個如果打開的話,就可以看到Druid在回收的連接的時候報錯信息:
首先會出現recyle error 的原因是因爲我咋Druid的配置中開啓了Druid的超時回收連接機制,Druid的內部回收線程會去回收Connection對象,所以會出現recyle error的報錯,但是其內部的根本原因是因爲Druid再回收的過程中遇到了錯誤,並且把日誌打印出來了(這個是Druid默認打開的),這個Druid打開的目的就是爲了告訴你,你的應用中有一些Connection回收的過程中失敗,但是並不影響Druid真正把這個Connection給強制回收,但是此時你的程序肯定是有問題的。
解決方法:
- 要麼是直接加上這個配置,把Druid打印回收Connection的錯誤日誌開關給關閉,但是這樣指標不治本,druidDataSource.setLogAbandoned(false);
- 自己去仔細的排查程序中可能會出現的Connection回收失敗的問題
(2)去排查自己程序中是否存在Connection回收失敗的問題
/**
* 獲取Druid連接對象
*
* @return
*/
public static DruidPooledConnection druidPooledConnection = null;
public static synchronized PrestoConnection getConnection() {
PrestoConnection conn = null;
try {
druidPooledConnection = druidDataSource.getConnection();
conn = (PrestoConnection) druidPooledConnection.getConnection();
} catch (SQLException e) {
log.error("創建Connection失敗異常信息 msg={}", " 原因", e);
}
return conn;
}
看一下我業務中關閉connection的時候,其實關閉的是PrestoConnection,但是其實應該是關閉DruidPooledConnection。業務中的錯誤代碼是這樣的:
try {
if (!resultSet.isClosed() && !statement.isClosed()){
resultSet.close();
statement.close();
log.info(" Close the ResultSet success: {}, Close the statement success: {}", resultSet.isClosed(), statement.isClosed());
}
if (!prestoConnection.isClosed()) {
prestoConnection.close();
log.info(" Close the prestoConnection success: {}", prestoConnection.isClosed());
}
} catch (SQLException e) {
log.error("關閉prestoConnection或者Statement失敗異常信息 msg={}", "原因", e);
e.printStackTrace();
}
把上面的關閉prestoConnection代碼如果改成關閉druidPooledConnection就好了
druidPooledConnection.close();
經過一系列的問題排查,最終定位,發現其真正的問題是因爲我的DruidPooledConnection沒有關閉(這個DruidPooledConnection是直接從DruidDataSource中獲取到的),但是我在程序中每次查詢完業務邏輯之後直接把我的PrestoConnection對象的連接給關閉了,關閉的不是我應該關閉的connection對象,所以Druid回收線程一直報錯,說明Connection泄漏了
(3)總結底層原理的原因
首先因爲不僅實現了Connection接口 而且還實現了PooledConnection 接口,但是PrestoConnection只實現了Connection接口,而我在使用的時候直接把PooledConnection強制轉化爲PrestoConnection了,那麼其實PooledConnection這個相當於是我的邏輯連接,而每次我直接在調用PrestoConnection.close()的時候,其實關閉的物理連接(因爲PrestoConnection並沒有實現PooledConnection這個接口,所以根據不具有Druid中的邏輯連接特性)
- PrestoConnection conn = (PrestoConnection) druidPooledConnection.getConnection();
- DruidPooledConnection extends PoolableWrapper implements javax.sql.PooledConnection, Connection
- public class PrestoConnection implements Connection