聊聊Druid連接池的內部原理及推薦配置
1.前言
一個正常的SQL執行流程為:
- Connection conn = DriverManager.getConnection();
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery(sql);
- 操作rs讀取數據;
- 關閉rs、stmt、conn。
示例代碼如下:
public static void main(String[] args){
try{
Class.forName("com.mysql.cj.jdbc.Driver"); //調用Class.forName()方法加載驅動程序
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB?useSSL=false", "root","*****"); //創建連接
Statement stmt = conn.createStatement(); //創建Statement對象
String sql = "select * from stu"; //要執行的SQL
ResultSet rs = stmt.executeQuery(sql);//創建數據對象
System.out.println("編號"+"\t"+"姓名"+"\t"+"年齡");
while (rs.next()){
System.out.print(rs.getInt(1) + "\t");
System.out.print(rs.getString(2) + "\t");
System.out.print(rs.getInt(3) + "\t");
System.out.println();
}
rs.close();
stmt.close();
conn.close();
}catch(Exception e){
}
}
但如果每次請求都要DriverManager.getConnection()新建連接和關閉連接,操作較重,費時費力,也影響了業務請求。 其實Connection對象是可以重復利用的(只要保證Connection借出后歸單一線程所有,其所創建的Statement和ResultSet在回收前都能關閉即可),這樣Connection被重新獲取后就可以跟新建的一樣,從而避免底層Socket連接的頻繁創建與關閉。數據庫連接池便應運而生。
DataSource定義了一個getConnection()的接口,具體實現可以是直接新建,也可以是從連接池里獲取。用戶使用完Connection后,要手動close(),而這個close()也是個邏輯語義。對于MySQL JDBC的ConnectionImpl來說,close()是物理關閉;而對于Druid的DruidPooledConnection來說,close()就是歸還。
2.Druid簡介
當我們有了連接池,應用程序啟動時就預先建立多個數據庫連接對象,然后將連接對象保存到連接池中。當客戶請求到來時,從池中取出一個連接對象為客戶服務。當請求完成時,客戶程序調用關閉方法,將連接對象放回池中[2]。跟現實生活中的共享單車是不是很像~
相比之下,連接池的優點顯而易見:
- 資源復用:因為數據庫連接可以復用,避免了頻繁創建和釋放連接引起的大量性能開銷,同時也增加了系統運行環境的平穩性;
- 提高性能:當業務請求時,因為數據庫連接在初始化時已經被創建,可以立即使用,而不需要等待連接的建立,減少了響應時間;
- 優化資源分配:對于多應用共享同一數據庫的系統而言,可在應用層通過數據庫連接池的配置,實現某一應用最大可用數據庫連接數的限制,避免某一應用獨占所有的數據庫資源;
- 連接管理:數據庫連接池實現中,可根據預先的占用超時設定,強制回收被占用連接,從而避免了常規數據庫連接操作中可能出現的資源泄露。
Druid連接池內部的數據結構如下(以minIdle=5,maxActive=10為例):
- 連接池采用LRU棧式置換策略(最近歸還的會被最先借出);
- poolingCount:池中可用的空閑連接;
- activeCount:已經借出去的連接數。兩者之和為所有連接數。此時池里有7個空閑連接,poolingCount=7;
- empty條件變量:連接池有空閑連接時會等待。獲取連接時如果無可用空閑連接會觸發signal;
- notEmpty條件變量:獲取連接時如果為空會等待,歸還或創建連接時會觸發signal。
3.初始化流程init()
觸發時機:首次getConnection()時或直接調用init()。
核心流程:
- 創建initialSize個連接;
- 啟動LogStatsThread、CreateConnectionThread、DestroyConnectionThread三個線程。
3.1 LogStatsThread線程(Druid-ConnectionPool-Log-)
如果timeBetweenLogStatsMillis > 0,則每隔timeBetweenLogStatsMillis打印一次stat日志。
3.2 CreateConnectionThread線程(Druid-ConnectionPool-Create-)
圖片
后臺負責創建連接的線程。監聽empty條件信號,收到信號后,如果滿足條件則創建一個新連接;如果不滿足,則忽略此次信號。
3.2.1 創建新連接的條件
// 防止創建超過maxActive數量的連接
if (activeCount + poolingCount >= maxActive) { //如果不滿足條件,則忽略此次信號并繼續await()
empty.await();
continue; //再次收到empty條件信號后,重新回到for (;;)處
}
3.2.2 createPhysicalConnection()創建連接流程
//創建一條連接,并初始化
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
//此處省略一萬行
try {
Connection conn = createPhysicalConnection(url, physicalConnectProperties); //創建一條物理連接
connectedNanos = System.nanoTime();
if (conn == null) {
throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
}
initPhysicalConnection(conn, variables, globalVariables); //初始化連接
initedNanos = System.nanoTime();
validateConnection(conn); //檢測一下
validatedNanos = System.nanoTime();
setFailContinuous(false);
setCreateError(null);
} //此處省略一萬行
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}
接下來再來看createPhysicalConnection(url, info)函數,它就是負責創建一條java.sql.Connection連接,如下:
public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
Connection conn;
if (getProxyFilters().size() == 0) {
conn = getDriver().connect(url, info); //創建一條連接
} else {
conn = new FilterChainImpl(this).connection_connect(info);
}
return conn;
}
3.2.3 put(holder, createTaskId)將連接放入連接池
將連接放入連接池尾部,并發送notEmpty條件信號。
//將連接放入連接池尾部,并發送notEmpty條件信號
private boolean put(DruidConnectionHolder holder, long createTaskId) {
lock.lock();
try {
if (poolingCount >= maxActive) {
return false;
}
connections[poolingCount] = holder; //放入連接池尾部
incrementPoolingCount(); //可用連接數poolingCount+1
notEmpty.signal(); //發送notEmpty條件信號
notEmptySignalCount++;
} finally {
lock.unlock();
}
return true;
}
3.3 DestroyConnectionThread線程(Druid-ConnectionPool-Destroy-)
圖片
定時掃描連接池進行探測和銷毀。DestroyConnectionThread每隔timeBetweenEvictionRunsMillis掃描一次連接池中的空閑連接:
- 如果物理存活時間超過phyTimeoutMillis,則直接銷毀;
- 如果( keepAlive && 空閑時間 >= keepAliveBetweenTimeMillis),則進行探測;
- 如果空閑時間 >= minEvictableIdleTimeMillis,則銷毀(但要保證留下minIdle個);而如果空閑時間超過maxEvictableIdleTimeMillis則必須進行銷毀;
- 如果( removeAbandoned == true && 連接借出時間 > removeAbandonedTimeoutMillis),則強制關閉其statement并歸還。
public void run() {
shrink(true, keepAlive); //探測與銷毀
if (isRemoveAbandoned()) { //檢查連接泄漏
removeAbandoned();
}
}
3.3.1 shrink(checkTime, keepAlive)流程
public void shrink(boolean checkTime, boolean keepAlive) {
try {
final int checkCount = poolingCount - minIdle;
final long currentTimeMillis = System.currentTimeMillis();
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder connection = connections[i];
if (phyTimeoutMillis > 0) { //如果連接的物理存活時間超過限值,將被銷毀。
long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis; //空閑時間
//如果空閑時間過短,直接跳過
if (idleMillis < minEvictableIdleTimeMillis
&& idleMillis < keepAliveBetweenTimeMillis
) {
break;
}
if (idleMillis >= minEvictableIdleTimeMillis) { //如果連接空閑時間超過minEvictableIdleTimeMillis
if (checkTime && i < checkCount) { //留尾部的minIdle個連接先不銷毀
evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) { //尾部的minIdle個連接如果連接空閑時間>maxEvictableIdleTimeMillis,也會被銷毀
evictConnections[evictCount++] = connection;
continue;
}
}
if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) { //如果連接空閑時間未超過minEvictableIdleTimeMillis,但超過了keepAliveBetweenTimeMillis就要進行探活
keepAliveConnections[keepAliveCount++] = connection;
}
}
} finally {
lock.unlock();
}
if (evictCount > 0) { //如果有需要銷毀的,則進行關閉連接操作。
for (int i = 0; i < evictCount; ++i) {
DruidConnectionHolder item = evictConnections[i];
Connection connection = item.getConnection();
JdbcUtils.close(connection);
destroyCountUpdater.incrementAndGet(this);
}
Arrays.fill(evictConnections, null);
}
if (keepAliveCount > 0) { //如果有需要探測的,則進行探測。探活的,則放回可用池;探不活的,直接關閉,并通知創建。
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {
//此處省略一萬行
}
}
if (needFill) {
lock.lock();
try {
int fillCount = minIdle - (activeCount + poolingCount + createTaskCount); //需要補充創建的連接個數
for (int i = 0; i < fillCount; ++i) {
emptySignal(); //給CreateConnectionThread發送empty條件信號來創建連接
}
} finally {
lock.unlock();
}
}
}
3.3.2 removeAbandoned()連接泄漏檢測
連接泄露檢查,打開removeAbandoned功能,連接從連接池借出后,長時間不歸還,將觸發強制關閉其staement并歸還。回收周期隨timeBetweenEvictionRunsMillis進行,如果連接借出時間起超過removeAbandonedTimeout,則強制關閉其staement并歸還。對性能會有一些影響,建議懷疑存在泄漏之后再打開,不建議在生產環境中使用。
//強制歸還泄漏(借出后長時間未歸還)的連接
public int removeAbandoned() {
long currrentNanos = System.nanoTime();
List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
activeConnectionLock.lock();
try {
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
for (; iter.hasNext();) {
DruidPooledConnection pooledConnection = iter.next();
if (pooledConnection.isRunning()) {
continue;
}
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000); //連接借出時間
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
abandonedList.add(pooledConnection);
}
}
} finally {
activeConnectionLock.unlock();
}
if (abandonedList.size() > 0) {
for (DruidPooledConnection pooledConnection : abandonedList) {
JdbcUtils.close(pooledConnection); //強制歸還
}
}
return removeCount;
}
4.獲取連接流程getConnection()
圖片
連接池最核心的功能就是連接的獲取與回收。我們直接看getConnectionDirect(),它負責獲取一個可用的連接。
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
for (;;) { //如果某次獲取到的連接無效,一般會丟棄該連接并重新獲取。
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis); //獲取連接
} catch (GetConnectionTimeoutException ex) {
//......
}
if (testOnBorrow) { //如果testOnBorrow=true,則進行探測。
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(poolableConnection.holder); //探測失敗,則丟棄此連接并重新獲取。
continue;
}
} else {
if (testWhileIdle) { //如果testWhileIdle=true且空閑時間>timeBetweenEvictionRunsMillis,則進行探測。
final DruidConnectionHolder holder = poolableConnection.holder;
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0 // unexcepted branch
) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(poolableConnection.holder); //如果探測失敗,則丟棄連接并重新獲取。
continue;
}
}
}
}
//是否打開連接泄露檢查。DestroyConnectionThread如果檢測到連接借出時間超過removeAbandonedTimeout,則強制歸還連接到連接池中。
//對性能會有一些影響,建議懷疑存在泄漏之后再打開,不建議在生產環境中使用。
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); //保存發起方的線程棧
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano(); //重置借出時間
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT); //放進activeConnections
} finally {
activeConnectionLock.unlock();
}
}
return poolableConnection;
}
}
4.1 getConnectionInternal()獲取一個連接
getConnectionInternal()負責從連接池獲取一個連接(但該連接并不保證可用),并包裝成DruidPooledConnection。
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
final int maxWaitThreadCount = this.maxWaitThreadCount;
DruidConnectionHolder holder;
for (boolean createDirect = false;;) {
try {
//......
if (maxWaitThreadCount > 0
&& notEmptyWaitThreadCount >= maxWaitThreadCount) { //如果等待獲取連接的線程數超過maxWaitThreadCount,則拋出異常
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
//從可用連接池里獲取連接,如果沒有則阻塞等待。
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
//......
} //......
break;
}
//......
holder.incrementUseCount(); //連接的使用次數+1
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); ////包裝成一個DruidPooledConnection對象
return poolalbeConnection;
}
4.2 takeLast()阻塞等待尾部連接
//阻塞等待尾部連接
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
try {
while (poolingCount == 0) { //如果沒有可用連接,就發送個empty條件信號給CreateConnectionThread,并等待notEmpty條件信號
emptySignal(); // send signal to CreateThread create connection
notEmptyWaitThreadCount++;
try {
notEmpty.await(); // signal by recycle or creator
} finally {
notEmptyWaitThreadCount--;
}
//......
}
} catch (InterruptedException ie) {
//......
}
//移除尾部連接
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
return last;
}
至此,向請求線程返回一個可用的連接DruidPooledConnection。
5.執行&異常處理
圖片
如下為Mybatis執行SQL的核心函數(SqlSessionTemplate$SqlSessionInterceptor.invoke()):
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//......
try {
Object result = method.invoke(sqlSession, args); //下調DruidPooledPreparedStatement.execute()執行SQL
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
//......
throw (Throwable)unwrapped; //如果發生異常,繼續上拋
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); //關閉連接,對應Druid是歸還。
}
}
return unwrapped;
}
}
- method.invoke()會通過Druid獲取連接,并調用DruidPooledPreparedStatement.execute();
- 執行結束后,close連接,此時會觸發Druid的連接歸還;
- 執行中如果發生異常,繼續向上拋。
5.1 DruidPooledPreparedStatement.execute()
此時,進入Druid連接中statement的execute(),如果發生異常進入checkException()。
public boolean execute() throws SQLException {
conn.beforeExecute();
try {
return stmt.execute(); //執行SQL
} catch (Throwable t) {
errorCheck(t);
throw checkException(t); //如果發生異常,調用DruidDataSource.handleConnectionException()對連接進行處理,并繼續上拋
} finally {
conn.afterExecute();
}
}
5.1.1 DruidDataSource.handleConnectionException()
public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t, String sql) throws SQLException {
//......
if (t instanceof SQLException) {
SQLException sqlEx = (SQLException) t;
// exceptionSorter.isExceptionFatal
if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) { //判斷是否是致命異常
handleFatalError(pooledConnection, sqlEx, sql); //如果是致命異常,則銷毀
}
throw sqlEx;
} else {
throw new SQLException("Error", t);
}
}
//對致命異常進行處理
protected final void handleFatalError(DruidPooledConnection conn, SQLException error, String sql ) throws SQLException {
final DruidConnectionHolder holder = conn.holder;
//......
if (requireDiscard) {
if (holder != null && holder.statementTrace != null) {
try {
for (Statement stmt : holder.statementTrace) { //關閉連接內的所有的statement
JdbcUtils.close(stmt);
}
} finally {
}
}
emptySignalCalled = this.discardConnection(holder); //銷毀
}
LOG.error("{conn-" + (holder != null ? holder.getConnectionId() : "null") + "} discard", error);
//......
}
6.回收連接流程recycle()
圖片
使用DruidPooledConnection連接進行SQL操作后,會調用DruidPooledConnection.recycle()進行回收操作。
public void recycle() throws SQLException {
DruidConnectionHolder holder = this.holder;
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.recycle(this);
//......
}
6.1 DruidDataSource.recycle()
/**
* 回收連接
* 1)重置連接;2)歸還到連接池。
*/
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.holder;
final Connection physicalConnection = holder.conn;
try {
//歸還前重置連接
holder.reset();
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) { //限制連接的最大使用次數。超過此值,會被直接關閉。
discardConnection(holder);
return;
}
if (testOnReturn) { //如果testOnReturn=true,歸還前也檢測下
//......
}
final long currentTimeMillis = System.currentTimeMillis();
if (phyTimeoutMillis > 0) { //檢測物理存活時間
long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
discardConnection(holder);
return;
}
}
lock.lock();
try {
result = putLast(holder, currentTimeMillis); //歸還到連接池
recycleCount++;
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(holder.conn);
}
} catch (Throwable e) {
//......
}
}
6.2 將連接放入可用連接池尾部,并發送notEmpty條件信號
//將連接放入可用連接池尾部,并發送notEmpty條件信號
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive || e.discard) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis; //重置最近活躍時間
connections[poolingCount] = e; //歸還到尾部
incrementPoolingCount();
notEmpty.signal();
notEmptySignalCount++;
return true;
}
至此,連接已成功回收。
7.總結
7.1 整個連接池的核心操作
- init()初始化:1)創建initialSize個連接;2)啟動LogStatsThread、CreateConnectionThread、DestroyConnectionThread三個線程;
- getConnection()獲取連接:獲取后會從連接池移除,Connection只能歸當前線程所用;
- recycle()回收連接:放回連接池后,其他線程就可以再次獲取該連接重復利用了。
7.2 條件信號協作
- 獲取連接時:如果連接池里沒有連接,會發出empty條件信號,并等待notEmpty條件信號。CreateConnectionThread收到empty信號后,如果滿足條件則創建一個新連接,也會發出notEmpty條件信號;如果不滿足,則忽略此次empty信號。
- 回收連接時:連接放回連接池后,會發出notEmpty條件信號。如果有請求在阻塞等待獲取連接,此時會被喚醒,從而獲取連接。
7.3 幾處檢測和銷毀邏輯
- 借出時:
如果testOnBorrow,則探測;
如果(testWhileIdle = true && 空閑時間 > timeBetweenEvictionRunsMillis)則進行探測;
- 執行時:
- 如果拋出異常且exceptionSorter判斷是致命異常,就調用handleFatalError()進行銷毀;
- 歸還時:
- 如果連接使用次數超過phyMaxUseCount,則銷毀;
- 如果testOnReturn=true,則探測;
- 如果連接建立時間走過phyTimeoutMillis,則銷毀;
- DestroyConnectionThread每隔timeBetweenEvictionRunsMillis掃描一次連接池中的空閑連接:
- 如果物理存活時間超過phyTimeoutMillis,則銷毀;
- 如果( keepAlive && 空閑時間 >= keepAliveBetweenTimeMillis),則進行探測;
- 如果空閑時間 >= minEvictableIdleTimeMillis,則銷毀(但要保證留下minIdle個);而如果空閑時間超過maxEvictableIdleTimeMillis則必須銷毀;
- 如果( removeAbandoned == true && 連接借出時間 > removeAbandonedTimeoutMillis),則被強制關閉其statement并歸還。
8.常用&推薦配置
8.1 常用配置
官方完整介紹見:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
圖片
圖片
圖片
8.2 推薦配置
<bean id="userdataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本屬性 -->
<property name="name" value="userdataSource" />
<property name="url" value="${userdataSource_url}" />
<property name="driverClassName" value="com.zhuanzhuan.arch.kms.jdbc.mysql.Driver" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="3" />
<property name="maxActive" value="20" />
<!-- 獲取連接的等待超時時間,單位是毫秒。配置了maxWait之后,缺省啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 -->
<property name="maxWait" value="60000" />
<!-- 探測的SQL -->
<property name="validationQuery" value="SELECT 1" />
<!-- 獲取連接時,執行validationQuery檢測連接是否有效 -->
<property name="testOnBorrow" value="false" />
<!-- 獲取連接時,如果空閑時間超過timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效 -->
<property name="testWhileIdle" value="true" />
<!-- 歸還連接時,執行validationQuery檢測連接是否有效 -->
<property name="testOnReturn" value="false" />
<!-- 定期檢測的時間間隔,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 定期檢測時,如果空閑時間超過此值則進行銷毀(但要保證留下minIdle個連接),單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
</bean>
此配置說明:
- 線程池剛啟動時會創建1個(initialSize)連接,隨著程序的運行,池不忙時也會保持最少3個(minIdle)空閑連接,但總連接數(包括空閑和在用)不超過20個(maxActive);
- 獲取連接時:
如果連接池沒有空閑連接,最長等待60秒(maxWait);
【主動】如果獲取到的連接空閑時間大于60秒(timeBetweenEvictionRunsMillis),則執行validationQuery檢測連接是否還有效(有效則使用,無效則銷毀);
- 執行SQL時:
- 【被動】如果發生致命異常(默認exceptionSorter=MySqlExceptionSorter,如CommunicationsException),則銷毀該連接;
- DestroyConnectionThread每隔60秒(timeBetweenEvictionRunsMillis)掃描一次連接池中的空閑連接:
- 【主動】如果空閑時間超過300秒(minEvictableIdleTimeMillis),則銷毀(但要保證留下minIdle=3個);而如果空閑時間超過7小時(maxEvictableIdleTimeMillis默認為7小時)則必須銷毀。
9.監控
Druid通過SPI開放了很多擴展點,我們架構部基于此封裝了監控組件,直接上報到Prometheus。效果如下:
圖片
圖片
關于作者
杜云杰,高級架構師,轉轉架構部負責人,轉轉技術委員會執行主席,騰訊云MVP。負責服務治理、MQ、云平臺、APM、IM、分布式調用鏈路追蹤、監控系統、配置中心、分布式任務調度平臺、分布式ID生成器、分布式鎖等基礎組件。微信號:waterystone,歡迎建設性交流。