一場因數據庫連接池配置引發的血案

數據庫連接池配置引發的危機

車禍現場:

xxx系統由於業務需求,所要部署的定時任務變的越來越多,於是決定利用Quartz框架來做一個任務調度管理模塊(採用集羣模式,並持久化任務調度信息,數據保存於mysql5.6)。
翌日,模塊開發完成,順利上線,但是上線一天後,服務報error了,如下:

JobStoreTX - MisfireHandler: Error handling misfires: Database error recovering from misfires.
Another error has occurred 
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed. 
which will not be reported to listeners!

於是排查問題,然後解決問題,第二次上線,這次上線新增瞭如下配置:

spring.datasource.test-while-idle: true
spring.datasource.time-between-eviction-runs-millis: 60000
spring.datasource.validation-query: SELECT 1

服務上線,運行正常

第二天運行正常
第三天運行正常
呵呵,小問題,手到擒來
運行一週後…
產品經理:高高呀,我怎麼覺得咱們服務響應速度有點慢呀
我:哦?是嗎,我去查查哈,可能是數據庫的數據量有點大了
運行1.5周後…
產品經理:高高,服務響應速度越來越慢了,你那邊排查完了沒
我(頓時一驚,靠,不會內存泄漏了吧!):好了,馬上就好啦

數日後,高高經過一番排查後,終於鎖定並解決了問題。

排查歷程:

問題1:
數據庫空閒連接自動斷開,導致任務運行異常發生
分析解決:

MySQL服務器默認的“wait_timeout”是28800秒即8小時,意味着如果一個連接的空閒時間超過8個小時,MySQL將自動斷開該連接,而連接池卻認爲該連接還是有效的(Quartz定時任務使用的是默認是maxIdleTime=0,永不丟棄),所以當應用申請使用該連接時,就會導致異常發生。
由於我們的數據庫連接池沒有配置連接有效性檢查,導致quartz使用的連接線程超時無效,所以我做了如下配置,定期使用連接池內的連接,使得它們不會因爲閒置超時而被 MySQL 斷開。
spring.datasource.test-while-idle: true
spring.datasource.time-between-eviction-runs-millis: 60000
spring.datasource.validation-query: SELECT 1
於是數據庫連接超時斷開的問題,就這樣被解決了

問題2:
服務整體響應速度變慢
分析解決:
1.排查內存泄漏問題
查看當前內存使用率,發現內存使用率僅有30%
考慮到剛剛上線的原因,然後查看內存使用率趨勢圖,並非內存泄漏的模樣,於是內存泄漏的可能性可以排除掉了
2.查看cpu使用率
經過排查cpu使用率正常
3.給服務打log,定位耗時長的代碼
觀察日誌發現耗時長的地方在sql查詢
4.查看數據庫連接配置(數據庫連接池默認使用Tomcat connection pool)

參數 參數說明
spring.datasource.test-while-idle (boolean) The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to validate, it will be dropped from the pool. The default value is false and this property has to be set in order for the pool cleaner/test thread is to run (also see timeBetweenEvictionRunsMillis)
spring.datasource.time-between-eviction-runs-millis (int) The number of milliseconds to sleep between runs of the idle connection validation/cleaner thread. This value should not be set under 1 second. It dictates how often we check for idle, abandoned connections, and how often we validate idle connections. The default value is 5000 (5 seconds).
spring.datasource.validation-query (String) The SQL query that will be used to validate connections from this pool before returning them to the caller. If specified, this query does not have to return any data, it just can’t throw a SQLException. The default value is null. If not specified, connections will be validation by the isValid() method. Example values are SELECT 1(mysql), select 1 from dual(oracle), SELECT 1(MS Sql Server)

仔細看了一下配置說明,初步推測可能是test-while-idle和time-between-eviction-runs-millis造成的影響,原因可能如下(此處是自己猜測,還未做深入探究 ):

  1. 檢查空閒,驗證空閒連接,刪除連接連接的頻率過高,佔用服務大量資源。(可能性較低,因爲資源監控並未發現系統的資源被過多佔用)
  2. 每次檢查空閒連接狀態後(運行 select 1),使得空閒連接變得不空閒,重新開始計算其空閒時間,進而導致大量的無用連接不被釋放,進而導致查詢變慢(可能性較大)

5.解決問題
刪除spring.datasource.test-while-idle 和 spring.datasource.time-between-eviction-runs-millis參數,新增spring.datasource.test-on-borrow=true、spring.datasource.validation-interval=60000。

參數 參數說明
spring.datasource.test-on-borrow (boolean) The indication of whether objects will be validated before being borrowed from the pool. If the object fails to validate, it will be dropped from the pool, and we will attempt to borrow another. In order to have a more efficient validation, see validationInterval. Default value is false
spring.datasource.validation-interval (long) avoid excess validation, only run validation at most at this frequency - time in milliseconds. If a connection is due for validation, but has been validated previously within this interval, it will not be validated again. The default value is 3000 (3 seconds).

test-on-borrow:在從池中借用對象之前驗證對象,若對象無法驗證,將其從池中刪除,並嘗試借用另一個。
validation-interval: 連接在進行驗證時,如果之前已在此時間間隔內進行了驗證,則不會再次進行驗證。
validation-interval值設置爲60000,是因爲我們的數據庫連接池使用了dbProxy,dbProxy空閒等待的時間比數據庫的空閒等待時間更短,從日誌來看空閒斷開的時間有時候會低至2min,故設置爲60000ms。

最開始其實我是拒絕使用test-on-borrow的,因爲網上查資料時看到說開啓這個參數後會影響性能,但是具體怎麼影響沒有說。
我想性能影響的點應該在:每次獲取連接對象前的驗證操作,當數據庫訪問量比較大的時候,纔會表現出性能問題。但考慮到這個服務主要是我們的管理後臺,不會有過高的訪問量,所以使用了這個參數。
再次上線並觀察了一段時間後,可以做出定論:服務響應速度變慢的問題解決了,性能問題並未感知到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章