首頁/ 娛樂/ 正文

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

開心一刻

一天在路邊看到一個街頭採訪

記者:請問,假如你兒子娶媳婦,給多少彩禮合適呢

大爺:一百萬吧,再給一套房,一輛車

大爺沉思一下,繼續說到:如果有能力的話再給老丈人配一輛車,畢竟他把女兒養這麼大也不容易

記者:那你兒子多大了?

大爺:我沒有兒子,有兩個女兒

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

問題背景

最近生產環境出現了一個問題,錯誤日誌類似如下

Failed to obtain JDBC Connection; nested exception is com。alibaba。druid。pool。GetConnectionTimeoutException: wait millis 1010, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user

日誌資訊提示的很明顯:獲取

JDBC Connection

失敗,因為從

druid

連線池獲取

connection

超時了

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

上圖的意思是:執行

select * from tbl_user

之前,需要從

druid

連線池中獲取一個

connect

而此時連線池的狀態是:一共 10 個啟用的

connect

,連線池最大建立 10 個

connect

,正在執行 sql 的

connect

也是 10 個

所以不能建立新的

connect

,那就等唄,一共等了

1010

毫秒,還是拿不到

connect

,就丟擲

GetConnectionTimeoutException

異常

簡單點說就是是連線池中連線數不夠,在規定的時間內拿不到

connect

那有人就說了:連線池的最大數量設定大一點,問題不就解決了嗎

最大連線數設定大一點只能說可以降低問題發生的機率,不能完全杜絕,因為網路情況、硬體資源的使用情況等等都是不穩定因素

今天要講的不是連線池大小問題,而是超時設定問題,我們慢慢往下看

問題復現

我們先來模擬下上述問題

MySQL

版本:

5。7。21

,隔離級別:RR

Druid

版本:

1。1。12

spring-jdbc

版本:

5。2。3。RELEASE

DruidDataSource 初始化

為了方便演示,就手動初始化了

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

多執行緒查詢

執行緒數多於連線池中

connect

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

模擬慢查詢

如果查詢飛快,15 個查詢,可能都用不上 10 個

connect

,所以我們需要簡單處理下

很簡單,給表加寫鎖唄:

LOCK TABLES tbl_user WRITE

給表

tbl_user

加上寫鎖,然後跑執行緒去查詢

tbl_user

的資料

異常演示

先鎖表,再啟動程式

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

可以看到,15 個執行緒中,有 5 個執行緒獲取

connect

失敗

Thread-13 Failed to obtain JDBC Connection; nested exception is com。alibaba。druid。pool。GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_userThread-5 Failed to obtain JDBC Connection; nested exception is com。alibaba。druid。pool。GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_userThread-10 Failed to obtain JDBC Connection; nested exception is com。alibaba。druid。pool。GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_userThread-7 Failed to obtain JDBC Connection; nested exception is com。alibaba。druid。pool。GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_userThread-8 Failed to obtain JDBC Connection; nested exception is com。alibaba。druid。pool。GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user

示例程式碼:

druid-timeout

時間配置項

Druid

中關於時間的配置項有很多,我們我們重點來看下如下幾個

maxWait

最大等待時長,單位是毫秒,-1 表示無限制

從連線池獲取

connect

,如果有空閒的

connect

,則直接獲取到,如果沒有則最長等待

maxWait

毫秒,如果還獲取不到,則丟擲

GetConnectionTimeoutException

異常

removeAbandonedTimeout

設定 druid 強制回收連線的時限,單位是秒

從連線池獲取到

connect

開始算起,超過此值後,

Druid

將強制回收該連線

官網也有說明:

連線洩漏監測

validationQueryTimeout

檢測連線是否有效的超時時間,單位是秒,-1 表示無限制

Druid

內部的一個檢測

connect

是否有效的超時時間,需要結合

validationQuery

來配置

timeBetweenEvictionRunsMillis

檢查空閒連線的頻率,單位是毫秒, 非正整數表示不進行檢查

空閒連線檢查的間隔時間,

Druid

池中的

connect

數量是一個動態從

minIdle

maxActive

擴張與收縮的過程

connect

使用高峰期,數量會從

minIdle

擴張到

maxActive

,使用低峰期,

connect

數量會從

maxActive

收縮到

minIdle

收縮的過程會回收一些空閒的

connect

,而

timeBetweenEvictionRunsMillis

就是檢查空閒連線的間隔時間

queryTimeout

執行查詢的超時時間,單位是秒,-1 表示無限制

最終會應用到

Statement

物件上,執行時如果超過此時間,則丟擲

SQLException

transactionQueryTimeout

執行一個事務的超時時間,單位是秒

minEvictableIdleTimeMillis

最小空閒時間,單位是毫秒,預設 30 分鐘

如果連線池中非執行中的連線數大於

minIdle

,並且某些連線的非執行時間大於

minEvictableIdleTimeMillis

,則連線池會將這部分連線設定成

Idle

狀態並關閉

maxEvictableIdleTimeMillis

最大空閒時間,單位是毫秒,預設 7 小時

如果

minIdle

設定的比較大,連線池中的空閒連線數一直沒有超過

minIdle

,那麼那些空閒連線是不是一直不用關閉?

當然不是,如果連線太久沒用,資料庫也會把它關閉(MySQL 預設 8 小時),這時如果連線池不把這條連線關閉,程式就會拿到一條已經被資料庫關閉的連線

為了避免這種情況,

Druid

會判斷池中的連線,如果非執行時間大於

maxEvictableIdleTimeMillis

,也會強行把它關閉,而不用判斷空閒連線數是否小於

minIdle

再看問題

其實前面的示例中設定了

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

獲取

connect

的最大等待時長是

10000

毫秒,也就是

10

removeAbandonedTimeout

設定是 7 秒

照理來說

connect

如果 7 秒未執行完

SQL

查詢,就會被

Druid

強制回收進連線池,那麼等待

10

秒應該能夠獲取到

connect

,為什麼會丟擲

GetConnectionTimeoutException

異常了?

這也就是文章標題中的超時設定問題

原始碼探究

很顯然,我們從

dataSource。init();

開始跟原始碼

會看到如下一塊程式碼

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

我們繼續跟

createAndStartDestroyThread();

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

重點來了,我們看下

DestroyTask

到底是怎麼樣一個邏輯

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

我們接著跟進

removeAbandoned

,關鍵程式碼

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

如果

connect

正在執行中是不會被強制回收進連線池的

回到我們的示例,

connect

都是在執行中,只是都在進行慢查詢,所以是無法被強制回收進連線池的,那麼其他執行緒自然在

maxWait

時間內無法獲取到

connect

至此文章標題中的問題的原因就找到了

那麼問題又來了:

removeAbandonedTimeout

作用在哪?

我們再仔細閱讀下:

連線洩漏監測

Druid

提供了

RemoveAbandanded

相關配置,目的是監測連線洩露,回收那些長時間遊離在連線池之外的空閒

connect

可能因為程式問題,導致申請的

connect

在處理完

sql

查詢後,不能回到連線池的懷抱,那麼這個

connect

處理遊離態,它真實存在,但後續誰也申請不到它,這就是連線洩露

removeAbandoned

的設計就是為了幫助這些洩露的

connect

回到連線池的懷抱

解決問題

開啟

removeAbandoned

對效能有影響,官方不建議在生產環境使用

那麼我們接受官方的建議,不開啟

removeAbandoned

(不配置即可,預設是關閉的)

為了不讓慢查詢佔用整個連線池,而拖垮整個應用,我們設定查詢超時時間

queryTimeout

有兩種方式,一個是設定

DataSource

queryTimeout

,另一個是設定

JdbcTemplate

queryTimeout

如果兩個都設定,最終生效的是哪個,為什麼?大家自己去分析,權當是給大家留個一個作業

這裡就配置

DataSource

queryTimeout

,給大家演示下效果

記一次 Druid 超時配置的問題 → 引發對 Druid 時間配置項的探究

可以看到,所有執行緒都獲取到了

connect

總結

1、

Druid

removeAbandoned

對效能有影響,不建議開啟

removeAbandoned

的開啟後的作用要捋清楚,而非簡單的過期強制回收

2、

Druid

的時間配置項有很多,不侷限於文中所講,但常用的就那麼幾個,其他的保持預設值就好

配置的時候一定要弄清楚各個配置項的具體作業,不要去猜!

3、查詢超時

queryTimeout

即可在

DataSource

配置,也可在

JdbcTemplate

配置

作者:青石路

相關文章

頂部