微信公衆號:跟着老萬學java
關注可瞭解更多的開發技巧。問題或建議,請公衆號留言;java
背景
工做這麼多年,數據庫鏈接池用了好久了,可是前幾天忽然被同事問到:「數據庫鏈接池的大小怎麼配?」,忽然感受內心慌的一逼,做爲這麼多年編程經驗的老鳥,絕逼不能認可說本身不會,只能淡定的說:測試環境併發少,巴拉巴拉……,因此能夠配置少點;生產環境併發高,因此數據庫鏈接池的鏈接數要配置的多點。而後在小弟崇拜的眼神中,趕忙偷溜。
我是絕逼不會說,本身是百度複製粘貼,一頓操做猛如虎,一看併發150的。mysql
回到正題,那麼數據庫鏈接池的大小究竟該怎麼設置呢?git
資源分析
當咱們尋找數據庫的性能瓶頸時,老是能夠將其歸爲三類:CPU、磁盤、網絡。把內存加進來也沒有錯,但比起磁盤和網絡,內存的帶寬要高出好幾個數量級,因此就先不加了。github
若是咱們無視磁盤和網絡,那麼結論就很是簡單。在一個8核的服務器上,設定鏈接/線程數爲8可以提供最優的性能,再增長鏈接數就會因上下文切換的損耗致使性能降低。數據庫一般把數據存儲在磁盤上,磁盤又一般是由一些旋轉着的金屬碟片和一個裝在步進馬達上的讀寫頭組成的。讀/寫頭同一時刻只能出如今一個地方,而後它必須「尋址」到另一個位置來執行另外一次讀寫操做。因此就有了尋址的耗時,此外還有旋迴耗時,讀寫頭須要等待碟片上的目標數據「旋轉到位」才能進行操做。使用緩存固然是可以提高性能的,但上述原理仍然成立。web
在這一時間段(即"I/O等待")內,線程是在「阻塞」着等待磁盤,此時操做系統能夠將那個空閒的CPU核心用於服務其餘線程。因此,因爲線程老是在I/O上阻塞,咱們可讓線程/鏈接數比CPU核心多一些,這樣可以在一樣的時間內完成更多的工做。sql
那麼應該多多少呢?
這要取決於磁盤。較新型的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
更多精彩,關注我吧。
本文分享自微信公衆號 - 跟着老萬學java(douzhe_2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。