我在研究HikariCP(一個數據庫鏈接池)時無心間在HikariCP的Github wiki上看到了一篇文章(即前面給出的連接),這篇文章有力地消除了我一直以來的疑慮,看完以後感受神清氣爽。故在此作譯文分享。
nginx
接下來是正文
數據庫鏈接池的配置是開發者們經常搞出坑的地方,在配置數據庫鏈接池時,有幾個能夠說是和直覺背道而馳的原則須要明確。web
1萬併發用戶訪問
想象你有一個網站,壓力雖然還沒到Facebook那個級別,但也有個1萬上下的併發訪問——也就是說差很少2萬左右的TPS。那麼這個網站的數據庫鏈接池應該設置成多大呢?結果可能會讓你驚訝,由於這個問題的正確問法是:數據庫
下面這個視頻是Oracle Real World Performance Group發佈的,請先看完:
http://www.dailymotion.com/video/x2s8uec緩存
(由於這視頻是英文解說且沒有字幕,我替你們作一下簡單的歸納:)
視頻中對Oracle數據庫進行壓力測試,9600併發線程進行數據庫操做,每兩次訪問數據庫的操做之間sleep 550ms,一開始設置的中間件線程池大小爲2048:服務器
初始的配置微信
壓測跑起來以後是這個樣子的:網絡
2048鏈接時的性能數據併發
每一個請求要在鏈接池隊列裏等待33ms,得到鏈接後執行SQL須要77msapp
此時數據庫的等待事件是這個熊樣的:ide
各類buffer busy waits
各類buffer busy waits,數據庫CPU在95%左右(這張圖裏沒截到CPU)
接下來,把中間件鏈接池減到1024(併發什麼的都不變),性能數據變成了這樣:
鏈接池降到1024後
獲取連接等待時長沒怎麼變,可是執行SQL的耗時減小了。
下面這張圖,上半部分是wait,下半部分是吞吐量
wait和吞吐量
能看到,中間件鏈接池從2048減半以後,吐吞量沒變,但wait事件減小了一半。
接下來,把數據庫鏈接池減到96,併發線程數仍然是9600不變。
96個鏈接時的性能數據
隊列平均等待1ms,執行SQL平均耗時2ms。
wait事件幾乎沒了,吞吐量上升。
沒有調整任何其餘東西,僅僅只是縮小了中間件層的數據庫鏈接池,就把請求響應時間從100ms左右縮短到了3ms。
But why?
爲何nginx只用4個線程發揮出的性能就大大超越了100個進程的Apache HTTPD?回想一下計算機科學的基礎知識,答案實際上是很明顯的。
即便是單核CPU的計算機也能「同時」運行數百個線程。但咱們都[應該]知道這只不過是操做系統用時間分片玩的一個小把戲。一顆CPU核心同一時刻只能執行一個線程,而後操做系統切換上下文,核心開始執行另外一個線程的代碼,以此類推。給定一顆CPU核心,其順序執行A和B永遠比經過時間分片「同時」執行A和B要快,這是一條計算機科學的基本法則。一旦線程的數量超過了CPU核心的數量,再增長線程數系統就只會更慢,而不是更快。
這幾乎就是真理了……
有限的資源
上面的說法只能說是接近真理,但還並無這麼簡單,有一些其餘的因素須要加入。當咱們尋找數據庫的性能瓶頸時,老是能夠將其歸爲三類:CPU、磁盤、網絡。把內存加進來也沒有錯,但比起磁盤和網絡,內存的帶寬要高出好幾個數量級,因此就先不加了。
若是咱們無視磁盤和網絡,那麼結論就很是簡單。在一個8核的服務器上,設定鏈接/線程數爲8可以提供最優的性能,再增長鏈接數就會因上下文切換的損耗致使性能降低。
數據庫一般把數據存儲在磁盤上,磁盤又一般是由一些旋轉着的金屬碟片和一個裝在步進馬達上的讀寫頭組成的。讀/寫頭同一時刻只能出如今一個地方,而後它必須「尋址」到另一個位置來執行另外一次讀寫操做。因此就有了尋址的耗時,此外還有旋迴耗時,讀寫頭須要等待碟片上的目標數據「旋轉到位」才能進行操做。使用緩存固然是可以提高性能的,但上述原理仍然成立。
在這一時間段(即"I/O等待")內,線程是在「阻塞」着等待磁盤,此時操做系統能夠將那個空閒的CPU核心用於服務其餘線程。因此,因爲線程老是在I/O上阻塞,咱們可讓線程/鏈接數比CPU核心多一些,這樣可以在一樣的時間內完成更多的工做。
那麼應該多多少呢?這要取決於磁盤。較新型的SSD不須要尋址,也沒有旋轉的碟片。可別想固然地認爲「SSD速度更快,因此咱們應該增長線程數」,偏偏相反,無需尋址和沒有旋迴耗時意味着更少的阻塞,因此更少的線程[更接近於CPU核心數]會發揮出更高的性能。只有當阻塞創造了更多的執行機會時,更多的線程數才能發揮出更好的性能。
網絡和磁盤相似。經過以太網接口讀寫數據時也會造成阻塞,10G帶寬會比1G帶寬的阻塞少一些,1G帶寬又會比100M帶寬的阻塞少一些。不過網絡一般是放在第三位考慮的,有些人會在性能計算中忽略它們。
上圖是PostgreSQL的benchmark數據,能夠看到TPS增加率從50個鏈接數開始變緩。在上面Oracle的視頻中,他們把鏈接數從2048降到了96,實際上96都過高了,除非服務器有16或32顆核心。
計算公式
下面的公式是由PostgreSQL提供的,不過咱們認爲能夠普遍地應用於大多數數據庫產品。你應該模擬預期的訪問量,並從這一公式開始測試你的應用,尋找最合適的鏈接數值。
鏈接數 = ((核心數 * 2) + 有效磁盤數)
核心數不該包含超線程(hyper thread),即便打開了hyperthreading也是。若是活躍數據所有被緩存了,那麼有效磁盤數是0,隨着緩存命中率的降低,有效磁盤數逐漸趨近於實際的磁盤數。這一公式做用於SSD時的效果如何還沒有有分析。
按這個公式,你的4核i7數據庫服務器的鏈接池大小應該爲((4 * 2) + 1) = 9。取個整就算是是10吧。是否是以爲過小了?跑個性能測試試一下,咱們保證它能輕鬆搞定3000用戶以6000TPS的速率併發執行簡單查詢的場景。若是鏈接池大小超過10,你會看到響應時長開始增長,TPS開始降低。
筆者注:
這一公式其實不只適用於數據庫鏈接池的計算,大部分涉及計算和I/O的程序,線程數的設置均可以參考這一公式。我以前在對一個使用Netty編寫的消息收發服務進行壓力測試時,最終測出的最佳線程數就恰好是CPU核心數的一倍。
公理:你須要一個小鏈接池,和一個充滿了等待鏈接的線程的隊列
若是你有10000個併發用戶,設置一個10000的鏈接池基本等於失了理智。1000仍然很恐怖。便是100也太多了。你須要一個10來個鏈接的小鏈接池,而後讓剩下的業務線程都在隊列裏等待。鏈接池中的鏈接數量應該等於你的數據庫可以有效同時進行的查詢任務數(一般不會高於2*CPU核心數)。
咱們常常見到一些小規模的web應用,應付着大約十來個的併發用戶,卻使用着一個100鏈接數的鏈接池。這會對你的數據庫形成極其沒必要要的負擔。
請注意
鏈接池的大小最終與系統特性相關。
好比一個混合了長事務和短事務的系統,一般是任何鏈接池都難以進行調優的。最好的辦法是建立兩個鏈接池,一個服務於長事務,一個服務於短事務。
再例如一個系統執行一個任務隊列,只容許必定數量的任務同時執行,此時併發任務數應該去適應鏈接池鏈接數,而不是反過來。
譯:http://suo.im/600LJ0
本文分享自微信公衆號 - IT老哥(dys_family)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。