數據庫連接池大小設置,你學會了嗎

微信公衆號:跟着老萬學java
關注可瞭解更多的開發技巧。問題或建議,請公衆號留言;

背景

工作這麼多年,數據庫連接池用了很久了,但是前幾天突然被同事問到:“數據庫連接池的大小怎麼配?”,突然感覺心裏慌的一逼,作爲這麼多年編程經驗的老鳥,絕逼不能承認說自己不會,只能淡定的說:測試環境併發少,巴拉巴拉……,所以可以配置少點;生產環境併發高,所以數據庫連接池的連接數要配置的多點。然後在小弟崇拜的眼神中,趕緊偷溜。
我是絕逼不會說,自己是百度複製粘貼,一頓操作猛如虎,一看併發150的。

回到正題,那麼數據庫連接池的大小究竟該怎麼設置呢?

資源分析

當我們尋找數據庫的性能瓶頸時,總是可以將其歸爲三類:CPU、磁盤、網絡。把內存加進來也沒有錯,但比起磁盤和網絡,內存的帶寬要高出好幾個數量級,所以就先不加了。

如果我們無視磁盤和網絡,那麼結論就非常簡單。在一個8核的服務器上,設定連接/線程數爲8能夠提供最優的性能,再增加連接數就會因上下文切換的損耗導致性能下降。數據庫通常把數據存儲在磁盤上,磁盤又通常是由一些旋轉着的金屬碟片和一個裝在步進馬達上的讀寫頭組成的。讀/寫頭同一時刻只能出現在一個地方,然後它必須“尋址”到另外一個位置來執行另一次讀寫操作。所以就有了尋址的耗時,此外還有旋迴耗時,讀寫頭需要等待碟片上的目標數據“旋轉到位”才能進行操作。使用緩存當然是能夠提升性能的,但上述原理仍然成立。

在這一時間段(即"I/O等待")內,線程是在“阻塞”着等待磁盤,此時操作系統可以將那個空閒的CPU核心用於服務其他線程。所以,由於線程總是在I/O上阻塞,我們可以讓線程/連接數比CPU核心多一些,這樣能夠在同樣的時間內完成更多的工作。

那麼應該多多少呢?
這要取決於磁盤。較新型的SSD不需要尋址,也沒有旋轉的碟片。可別想當然地認爲“SSD速度更快,所以我們應該增加線程數”,恰恰相反,無需尋址和沒有旋迴耗時意味着更少的阻塞,所以更少的線程[更接近於CPU核心數]會發揮出更高的性能。只有當阻塞創造了更多的執行機會時,更多的線程數才能發揮出更好的性能。

網絡和磁盤類似。通過以太網接口讀寫數據時也會形成阻塞,10G帶寬會比1G帶寬的阻塞少一些,1G帶寬又會比100M帶寬的阻塞少一些。不過網絡通常是放在第三位考慮的,有些人會在性能計算中忽略它們。

線程數設置多少,核心是分析CPU數目以及請求執行過程中哪些地方會堵塞耗時等因素之間的權衡;
比如都是CPU計算的任務,那麼增大線程數對提高整體速度是沒有用處的。

線程公式

下面的公式是由PostgreSQL提供的,不過我們認爲可以廣泛地應用於大多數數據庫產品。你應該模擬預期的訪問量,並從這一公式開始測試你的應用,尋找最合適的連接數值。

連接數 = ((核心數 * 2) + 有效磁盤數)

按這個公式,你的4核i7數據庫服務器的連接池大小應該爲((4 * 2) + 1) = 9。取個整就算是是10吧。是不是覺得太小了?跑個性能測試試一下,我們保證它能輕鬆搞定3000用戶以6000TPS的速率併發執行簡單查詢的場景。如果連接池大小超過10,你會看到響應時長開始增加,TPS開始下降。

擴展說明:
這一公式其實不僅適用於數據庫連接池的計算,大部分涉及計算和I/O的程序,線程數的設置都可以參考這一公式。我之前在對一個使用Netty編寫的消息收發服務進行壓力測試時,最終測出的最佳線程數就剛好是CPU核心數的一倍。

公理:你需要一個小連接池,和一個充滿了等待連接的線程的隊列

如果你有10000個併發用戶,設置一個10000的連接池是非常不明智的。1000仍然很恐怖。即是100也太多了。你需要一個10來個連接的小連接池,然後讓剩下的業務線程都在隊列裏等待。連接池中的連接數量應該等於你的數據庫能夠有效同時進行的查詢任務數(通常不會高於2*CPU核心數)。

我們經常見到一些小規模的web應用,應付着大約十來個的併發用戶,卻使用着一個100連接數的連接池。這會對你的數據庫造成極其不必要的負擔。

請注意:

連接池的大小最終與系統特性相關。
比如一個混合了長事務和短事務的系統,通常是任何連接池都難以進行調優的。最好的辦法是創建兩個連接池,一個服務於長事務,一個服務於短事務。

再例如一個系統執行一個任務隊列,只允許一定數量的任務同時執行,此時併發任務數應該去適應連接池連接數,而不是反過來。

HikariCP連接池推薦配置

<!-- Hikari Datasource -->
 <bean id="dataSourceHikari" class="com.zaxxer.hikari.HikariDataSource"  destroy-method="shutdown">
  <!-- <property name="driverClassName" value="${db.driverClass}" /> --> <!-- 無需指定,除非系統無法自動識別 -->
  <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
  <property name="username" value="${db.username}" />
  <property name="password" value="${db.password}" />
   <!-- 連接只讀數據庫時配置爲true, 保證安全 -->
  <property name="readOnly" value="false" />
  <!-- 等待連接池分配連接的最大時長(毫秒),超過這個時長還沒可用的連接則發生SQLException, 缺省:30秒 -->
  <property name="connectionTimeout" value="30000" />
  <!-- 一個連接idle狀態的最大時長(毫秒),超時則被釋放(retired),缺省:10分鐘 -->
  <property name="idleTimeout" value="600000" />
  <!-- 一個連接的生命時長(毫秒),超時而且沒被使用則被釋放(retired),缺省:30分鐘,建議設置比數據庫超時時長少30秒,參考MySQL wait_timeout參數(show variables like '%timeout%';) -->
  <property name="maxLifetime" value="1800000" />
  <!-- 連接池中允許的最大連接數。缺省值:10;推薦的公式:((core_count * 2) + effective_spindle_count) -->
  <property name="maximumPoolSize" value="15" />
 </bean>

細心的讀者可能發現,這裏沒有配置minimumIdle參數,這是因爲如果minimumIdle小於maximumPoolSize,HikariCP將竭盡全力快速有效地添加其他連接。但是,爲了獲得最佳性能和對峯值需求的響應能力,我們建議不要設置此值,而應讓HikariCP充當固定大小的連接池。
這點可以類比jvm參數中,Xms和Xmx一般推薦設置的一樣,這樣可以減輕程序運行過程中堆伸縮帶來的壓力。

https://github.com/brettwooldridge/HikariCP

minimumIdle
This property controls the minimum number of idle connections that HikariCP tries to maintain in the pool. If the idle connections dip below this value and total connections in the pool are less than maximumPoolSize, HikariCP will make a best effort to add additional connections quickly and efficiently. However, for maximum performance and responsiveness to spike demands, we recommend not setting this value and instead allowing HikariCP to act as a fixed size connection pool. Default: same as maximumPoolSize

最後

牢記數據庫連接池的計算公式:
                   連接數 = ((核心數 * 2) + 有效磁盤數)
根據資源使用,線程等待時間分析公式的合理性。
另外最大連接數和最小連接數設置相等

恭喜看完本篇,是不是突然有種想出去裝逼的衝動。去吧,少年,學習知識就是這麼快樂。

 

參考:
https://www.jianshu.com/p/15b846107a7c
https://github.com/brettwooldridge/HikariCP

更多精彩,關注我吧。

 

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