Java Mysql連線池配置和案例分析--超時異常和處理
前言:
最近在開發服務的時候, 發現服務只要一段時間不用, 下次首次訪問總是失敗. 該問題影響雖不大, 但終究影響使用者體驗. 觀察日誌後發現, mysql連線因長時間空閒而被關閉, 使用時沒有死鏈檢測機制, 導致sql執行失敗.
問題的表層根源, 看似簡單, 但實際解決之路, 卻顯得有些曲折坎坷. 因此有必須分析下本質的原因, 以及Java Mysql連線池的處理策略和相關的配置項.
異常現象和問題本源:
服務的持久層依賴mysql, 採用連線池的機制來優化效能. 但服務空閒一段時間(切確地講是mysql connection空閒一段時間), 下次使用時執行sql失敗.
具體的異常, 可反映到具體的異常日誌:
當然除了異常的原因以外, 裡面也提供了一個解決方案.
1 2 3 4 5 6 |
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
The
last packet successfully received from the server was 47 , 302 , 202 milliseconds
ago.
The
last packet sent successfully to the server was 47 , 302 , 202 milliseconds
ago.
is
longer than the server configured value of 'wait_timeout' .
You
should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client
timeouts,
or
using the Connector/J connection property 'autoReconnect=true' to
avoid this problem.
|
暫且不管這個autoReconnect=true
Mysql程序對擁有的資源, 都有其相應的回收策略. 比如空閒連線, 超過一段時間間隔後, mysql就會主動關閉該連線. 而這個時間間隔就由wait_timeout和interactive_timeout來確定.
Mysql伺服器關閉該空閒連線後,而客戶端的連線並沒有主動去關閉,導致首次使用時,執行失敗.
順便談談mysql裡面涉及的兩個概念: 互動式連線和非互動式連線.
網上爭議和吐槽都很多, 但從官網的解讀來看, 或許只是mysql_real_connect()呼叫時, 是否開啟CLIENT_INTERACTIVE開關. 再深入的區別解讀, 或許已經沒有意義了.
互動式連線, 由interactive_timeout來確定, mysql的命令列工具即是該型別.
非互動式連線, 則由wait_timeout來決定, jdbc/odbc等方式的連線即為該型別, 一般而言, Java Mysql連線池屬於該類別,需關注wait_timeout項.
曲折的路途:
先後採用兩種方式,一種是設定重連選項,另一種是連線池主動淘汰.
• 設定重連選項
按照異常中附帶的建議,再jdbc的url中添加了屬性autoReconnect=true.
原本以為妥妥的,沒想到事與願違, 還是出現了類似的錯誤.
參照網上革命戰友的說法, mysql5以後autoReconnet=true已經失效了, 具體可以參考Bug #5020.
• 連線池主動淘汰
在配置的連線池中,按一定的規則淘汰掉空閒連線,降低死鏈被使用的概率.
1). testBeforeUse/testAfterUse
testBeforeUse顧名思義, 就是把連線從連線池中取出時, 先執行validation sql,再執行目標的sql語句. testAfterUse則剛好相反,再放回連線池時進行檢測.
雖然每次執行,都會額外的執行一次validation sql,但還是完美的解決上述的問題。不過需要注意的是,其代價昂貴,在高併發情況下需慎用.
2). 定時任務+按空閒閾值淘汰
按一定時間間隔執行清理任務,設定空閒時間的上限,一旦檢測到連線其空閒時間超過該閾值,則主動關閉掉. 當然定時週期和空閒閾值都小於wait_timeout值.
3). 定時任務+validation sql檢測淘汰
按一定時間間隔執行清理任務,對空閒連線進行validation sql檢測, 若失敗則主動關閉. 這種方式是testBeforeUse/testAfterUse的有益補充, 有效減少了執行validation sql的次數,解決了代價高昂的窘境. 當然定時週期小於wait_timeout值.
DBCP舉例:
我們選用DBCP作為連線池配置的樣例, 看看它如何實現上述談到的主動淘汰策略的.
先來看下DBCP關於空閒連線處理的配置項:
validationQuery | SQL查詢,用來驗證從連線池取出的連線,在將連線返回給呼叫者之前.如果指定,則查詢必須是一個SQL SELECT並且必須返回至少一行記錄 | |
testOnBorrow | true | 指明是否在從池中取出連線前進行檢驗,如果檢驗失敗,則從池中去除連線並嘗試取出另一個.注意: 設定為true後如果要生效,validationQuery引數必須設定為非空字串 |
testOnReturn | false | 指明是否在歸還到池中前進行檢驗注意: 設定為true後如果要生效,validationQuery引數必須設定為非空字串 |
testWhileIdle | false | 指明連線是否被空閒連接回收器(如果有)進行檢驗.如果檢測失敗,則連線將被從池中去除.注意: 設定為true後如果要生效,validationQuery引數必須設定為非空字串 |
timeBetweenEvictionRunsMillis | -1 | 在空閒連接回收器執行緒執行期間休眠的時間值,以毫秒為單位. 如果設定為非正數,則不執行空閒連接回收器執行緒 |
numTestsPerEvictionRun | 3 | 在每次空閒連接回收器執行緒(如果有)執行時檢查的連線數量 |
minEvictableIdleTimeMillis | 1000 * 60 * 30 | 連線在池中保持空閒而不被空閒連接回收器執行緒(如果有)回收的最小時間值,單位毫秒 |
注:如果想看更多DBCP的配置項, 請參考博文: DBCP的引數配置;
1). testBeforeUse/testAfterUse
1 2 3 4 5 |
< bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
< property name="testOnBorrow"
value="true" />
< property name="testOnReturn"
value="false" />
</ bean >
|
2). 定時任務+按空閒閾值淘汰
1 2 3 4 5 6 7 8 9 10 |
< bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!--
validation query -->
< property name="validationQuery"
value="SELECT 1" />
<!--
定時週期間隔 -->
< property name="timeBetweenEvictionRunsMillis"
value="90000" />
< property name="numTestsPerEvictionRun"
value="3" />
<!--
空閒連線的生存閾值 -->
< property name="minEvictableIdleTimeMillis"
value="1800000" />
</ bean >
|
3). 定時任務+validation sql檢測淘汰
1 2 3 4 5 6 7 8 9 10 11 |
< bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!--
validation query -->
< property name="validationQuery"
value="SELECT 1" />
<!--
定時週期間隔 -->
< property name="timeBetweenEvictionRunsMillis"
value="900000" />
< property
|