一次項目實踐中DBCP數據庫鏈接池性能優化

關於數據庫鏈接池DBCP的關注源於剛剛結束的一輪測試,測試內容是衡量某Webserver服務建立用戶接口的性能。這是一款典型的tomcat應用,使用的測試工具是Grinder。DBCP做爲tomcat服務器經常使用的數據庫鏈接池,其性能表現直接關乎應用的性能。java

1.遇到的問題web

       當併發量增長到100時,該接口出現瓶頸,此時TPS接近400,以下圖。可是服務端CPU和內存等資源並未達到瓶頸,服務器CPU使用率僅爲30%,內存使用率爲40%。監控到的javaMethod慢方法爲incrAppAccountsSize(),該方法的平均響應時間達206ms。該方法的功能是增長app下的用戶數,每當咱們在該app下建立一個用戶,相應app的總用戶數就會加1,緩存中響應的數據也會更新。所以該方法涉及數據庫和redis的相關操做。redis


數據庫的插入操做一般比較耗時,添加數據庫監控後,該接口相關sql語句執行時間以下,可見相關操做的sql語句的平均耗時在20ms左右,然而慢方法的耗時卻在200ms左右,所以性能瓶頸不在sql語句。sql


所以有必要分析下代碼執行過程當中的堆棧信息,看看慢方法涉及哪些調用。經過jstack dump出現場的堆棧信息以下。
數據庫

2.數據庫鏈接池DBCPapi

         衆所周知,數據庫的最大鏈接數是有限的,服務端與數據庫創建鏈接的過程也是很是消耗資源的,所以高效利用數據庫鏈接是很是必要的。java提供了一套專門與數據庫打交道的api--jdbc,基於jdbc開發人員能夠實現服務端與數據庫端的通訊。每次執行數據庫操做一般須要經歷創建數據庫鏈接、執行數據庫操做、斷開數據庫鏈接三步。當數據庫併發訪問量大時或請求頻繁時,頻繁的創建鏈接再斷開鏈接會給服務端帶來很大的額外開銷,嚴重製約着服務端的性能。緩存

         所以,數據庫鏈接須要一套維護策略,基於不一樣的維護策略出現了不少開源的數據庫鏈接組件,DBCP(DataBase Connection Pool)就是其中一個,也據信是目前性能最好的數據庫鏈接組件。除了DBCP之外還有C3P0,、proxool等也是比較常見的。在必定的維護策略下,數據庫鏈接能夠實現複用以及再回收。DBCP的使用很簡單引入commons-dbcp依賴,並配置好相關參數便可。DBCP的相關配置參數以下,tomcat

  • maxActive:最大鏈接數服務器

  • maxIdle:最大空閒鏈接數併發

  • minIdle:最小空閒鏈接數

  • initialSize:初始鏈接數

  • minEvictableIdleTimeMillis:對鏈接進行有效性校驗的週期

        maxActive的值一般根據本身應用的峯值併發量進行設置,當併發量高於該值後超過的請求會排隊等待鏈接釋放;當併發量介於maxIdle和maxActive之間時,使用結束的鏈接會被當即斷開,新來的請求會從新建立鏈接;當併發量介於minIdle和maxIdle之間時,新來的請求會從鏈接池充獲取一個已經創建的且空閒的鏈接,迅速實現鏈接複用。所以一般狀況下應用的併發量應該維持在minIdle與maxIdle之間。這樣才能保證新來的請求迅速獲取一個已經創建的鏈接。一般狀況下initialSize會小於或等於minIdle當服務器重啓後服務器會與數據庫節點保持initialSize個鏈接,當併發量超多initialSize後鏈接數會依次上漲至minIdle、maxIdle和maxActive,當鏈接空閒時根據回收策略鏈接會被回收,並最終回落至minIdle。

3.問題分析

        結合DBCP以及第一節中利用jstack dump出的堆棧信息,可見服務端一直處於鏈接斷開的過程當中。而DBCP創建鏈接的速度比斷開鏈接快,所以服務端一直卡在資源釋放的階段。服務端的DBCP的相關配置以下:

  • maxActive:500

  • maxIdle:50

  • minIdle:30

  • initialSize:30

       併發訪問量爲160,在maxIdle和maxActive之間,所以當一個鏈接使用結束後因爲當前鏈接數大於maxIdle鏈接沒法被複用會被當即斷開,新來一個請求也沒法獲取一個空閒的鏈接須要從新創建一個新的鏈接,因爲斷開鏈接的響應時間較慢,斷開鏈接都在等待資源的釋放,大量的線程出現排隊,這樣就出現了經過jstack看到的,線程被block住的現象。針對這種問題有兩種優化思路:

  1. 提升鏈接池的複用率。

  2. 增長空閒鏈接的數量。

        經過提升鏈接複用率來解決這種問題的前提是數據庫操做的執行速度夠快,監控數據庫sql執行速度在20ms左右,若是多個數據庫操做排隊對性能影響不大,基於這種思路能夠下降maxActive的值。當併發訪問量達到上限後再也不分配更多的數據庫鏈接,而是等待前一個鏈接使用結束,因爲maxActive與maxIdle的差值變小,所以須要斷開的鏈接的數量變少,這樣能夠避免因爲鏈接的斷開性能較差致使大量線程被block住的狀況。將maxActive的值修改成100後,160併發建立用戶的性能以下。

慢方法incrAppAccountsSize的響應時間由800多毫秒,減少到80多毫秒,經過jstack抓取現場堆棧信息能夠看到有些線程被block在getConnection狀態,線程獲取鏈接出現排隊,可是因爲數據庫操做響應時間很快,這種線程被block在getConnection狀態的時間並不長。

        經過增長空閒鏈接的數量來也能夠改善頻繁斷開鏈接所帶來的性能問題,因爲當前併發量超過了最大空閒鏈接數,增長空閒鏈接數能夠減小斷開鏈接的數量,提升複用率。可是增長空閒鏈接的數量會佔用寶貴的數據庫鏈接資源,影響服務擴展。將maxIdle大小調整爲200後,160併發建立用戶的性能以下。

以上數據代表,經過這兩種方式能夠有效的提升慢方法的響應時間。經過增長空閒鏈接的數量帶來的性能提高優於增長複用率,由於增長空閒鏈接的數量能夠避免排隊耗時。單純的增長maxIdle的值會使得空閒鏈接的數量增長。併發鏈接也不會一直維持在maxIdle這個水平,若是當前的併發壓力減少了,DBCP自己也有鏈接回收策略。空閒鏈接的回收是經過這兩個參數來實現的。

  • minEvictableIdleTimeMillis:空閒鏈接過時時間

  • timeBetweenEvictionRunsMillis:空閒鏈接回收觸發週期

        當鏈接池中的空閒鏈接空閒了minEvictableIdleTimeMillis這麼長時間後,該鏈接會被置爲可回收狀態。DBCP會按照timeBetweenEvictionRunsMillis的時間進行週期回收。最在沒有額外壓力的前提下終空閒鏈接穩定到minIdle水平。

4.總結

       數據庫鏈接池的maxIdle和maxActive之間的差值不宜不過大。若是數據庫操做響應時間很快的話能夠提升鏈接複用率,可是這麼作會出現鏈接排隊的狀況。若是數據庫操做的響應時間較慢能夠增長空閒鏈接的數量。因爲數據庫總的鏈接數是必定的,單個服務的具體數據庫鏈接配額應該根據服務的壓力進行調整。

 

原創文章

禁止其餘公衆帳號轉載

相關文章
相關標籤/搜索