前言:
最近在開發服務的時候, 發現服務只要一段時間不用, 下次首次訪問總是失敗. 該問題影響雖不大, 但終究影響用戶體驗. 觀察日志后發現, mysql連接因長時間空閑而被關閉, 使用時沒有死鏈檢測機制, 導致sql執行失敗.
問題的表層根源, 看似簡單, 但實際解決之路, 卻顯得有些曲折坎坷. 因此有必須分析下本質的原因, 以及Java Mysql連接池的處理策略和相關的配置項.
異常現象和問題本源:
服務的持久層依賴mysql, 采用連接池的機制來優化性能. 但服務空閑一段時間(切確地講是mysql connection空閑一段時間), 下次使用時執行sql失敗.
具體的異常, 可反映到具體的異常日志:
當然除了異常的原因以外, 里面也提供了一個解決方案.
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
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="testOnBorrow" value="true" /> <property name="testOnReturn" value="false" /> </bean>
2). 定時任務+按空閑閾值淘汰
<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檢測淘汰
<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 name="numTestsPerEvictionRun" value="3" /> <!-- 開啟空閑狀態檢測 --> <property name="testWhileIdle" value="true" /> </bean>
事實上,這三種策略除了單獨配置,還可以組合,甚至全部啟用.
附帶:
其實mysql有很多特性, 我們未必真得熟悉.
如何展示mysql的配置變量值:
注:可以看到wait_timeout為28800(28800=60 * 60 * 8), 這就是著名的8小時問題的出處.
如何修改變量:
注:需要注意全局變量和會話變量,以及如何生效.
如何查看mysql的連接數:
總結:
算是一個總結歸納之作, 對於很多革命戰友提議說把wait_timeout設長, 我感覺還是治標不治本. 因此就沒闡述這個想法, 權當筆記.
公眾號&游戲站點:
個人微信公眾號: 木目的H5游戲世界
個人游戲作品集站點(尚在建設中...): www.mmxfgame.com, 也可直接ip訪問: http://120.26.221.54/.