文章翻譯整理自: github.com/brettwooldr…
目錄
- 一、筆者前言
- 二、正菜開始
- 三、假設你的服務有1萬併發的訪問
- 四、為啥有這種效果?
- 五、其他應該考慮到的因素
- 六、連線數計算公式
- 七、結論:你需要的是一個小連線池,和一個等待連線的執行緒佇列
- 八、額外需要注意的點
一、筆者前言
基本上來說,大部分專案都需要跟資料庫做互動,那麼,資料庫連線池的大小設定成多大合適呢?
一些開發老鳥可能還會告訴你:沒關係,儘量設定的大些,比如設定成 200,這樣資料庫效能會高些,吞吐量也會大些!
你也許會點頭稱是,真的是這樣嗎?看完這篇文章,也許會顛覆你的認知哦!
二、正菜開始
可以很直接的說,關於資料庫連線池大小的設定,每個開發者都可能在一環節掉進坑裡,事實上呢,大部分程式設計師可能都會依靠自己的直覺去設定它的大小,設定成 100 ?思量許久後,自顧自想,應該差不多吧?
三、假設你的服務有1萬併發的訪問
不妨意淫一下,你手裡有個網站,併發壓力雖然還沒到 Facebook 那個級別,但是呢?也有個1萬上下的併發量!也就是說差不多2萬左右的 TPS。
那麼問題來了!這個網站的資料庫連線池應該設定成多大合適呢?
其實這個問法本身就是有問題的,我們需要反過來問,正確問法應該是:
“這個網站的資料庫連線池應該設定成多小合適呢?”
PS: 這裡有一個 Oracle 效能小組釋出的簡短視訊,連結地址為 www.dailymotion.com/video/x2s8u…
口述一下,視訊中對 Oracle 資料庫進行了壓力測試,模擬 9600 個併發執行緒來運算元據庫,每兩次資料庫操作之間 sleep 550ms,注意,視訊中剛開始設定的執行緒池大小為 2048。
讓我們來看看資料庫連線池的大小為 2048 效能測試結果的鬼樣子:
每個請求要在連線池佇列裡等待 33ms,獲得連線之後,執行SQL需要耗時77ms, CPU 消耗維持在 95% 左右;
接下來,我們將連線池的大小改小點,設定成 1024,其他測試引數不變,結果咋樣?
"這裡,獲取連線等待時長基本不變,但是 SQL 的執行耗時降低了!"
哎呦,有長進哦!
接下來,我們再設定小些,連線池的大小降低到 96,併發數等其他引數不變,看看結果如何:
每個請求在連線池佇列中的平均等待時間為 1ms, SQL 執行耗時為 2ms.
我去!什麼鬼?
我們沒調整任何東西,僅僅只是將資料庫連線池的大小降低了,這樣,就能把之前平均 100ms 響應時間縮短到了 3ms。吞吐量指數級上升啊!
你這也太溜了!
四、為啥有這種效果?
我們不妨想一下,為啥 Nginx 內部僅僅使用了 4 個執行緒,其效能就大大超越了 100 個程式的 Apache HTTPD 呢?追究其原因的話,回想一下電腦科學的基礎知識,答案其實非常明顯。
要知道,即使是單核 CPU 的計算機也能“同時”執行著數百個執行緒。但我們其實都知道,這只不過是作業系統快速切換時間片,跟我們玩的一個小把戲罷了。
一核 CPU同一時刻只能執行一個執行緒,然後作業系統切換上下文,CPU 核心快速排程,執行另一個執行緒的程式碼,不停反覆,給我們造成了所有程式同時執行假象。
其實,在一核 CPU 的機器上,順序執行A和B永遠比通過時間分片切換“同時”執行A和B要快,其中原因,學過作業系統這門課程的童鞋應該很清楚。一旦執行緒的數量超過了 CPU 核心的數量,再增加執行緒數系統就只會更慢,而不是更快,因為這裡涉及到上下文切換耗費的額外的效能。
說到這裡,你應該恍然大悟了 ……
五、其他應該考慮到的因素
上小節中說到了主要原因,但其實沒有這麼簡單,我們還需要考慮到一些其他的因素。
當我們在尋找資料庫的效能瓶頸時,大致可歸為三類:
- CPU
- 磁碟 IO
- 網路 IO
也許你會說,還有記憶體這一因素?記憶體的確是需要考慮的,但是比起磁碟IO和網路IO,稍顯微不足道,這裡就不加了。
假設我們不考慮磁碟 IO 和網路 IO,就很好定論了,在一個 8 核的伺服器上,資料庫連線數/執行緒數設定為 8 能夠提供最優的效能,如果再增加連線數,反而會因為上下文切換導致效能下降。
大家都知道,資料庫通常把資料儲存在磁碟上,而磁碟呢,通常是由一些旋轉著的金屬碟片和一個裝在步進馬達上的讀寫頭組成的。讀/寫頭同一時刻只能出現在一個位置,當它需要再次執行讀寫操作時,它必須“定址”到另外一個位置才能完成任務。所以呢?這裡就有了定址的耗時,此外還有旋轉耗時,讀寫頭需要等待磁碟碟片上的目標資料“旋轉到位”才能進行讀寫操作。使用快取當然是能夠提升效能的,但上述原理仍然適用。
在這段("I/O等待")時間內,執行緒是處於“阻塞”等待狀態,也就是說沒幹啥正事!此時作業系統可以將這個空閒的CPU 核心用於服務其他執行緒。
這裡我們可以總結一下,當你的執行緒處理的是 I/O 密集型業務時,便可以讓執行緒/連線數設定的比 CPU核心大一些,這樣就能夠在同樣的時間內,完成更多的工作,提升吞吐量。
那麼問題又來了?
大小設定成多少合適呢?
這要取決於磁碟,如果你使用的是 SSD 固態硬碟,它不需要定址,也不需要旋轉碟片。打住打住!!!你千萬可別理所當然的認為:“既然SSD速度更快,我們把執行緒數的大小設定的大些吧!!”
結論正好相反!無需定址和沒有旋迴耗時的確意味著更少的阻塞,所以更少的執行緒(更接近於CPU核心數)會發揮出更高的效能。只有當阻塞密集時,更多的執行緒數才能發揮出更好的效能。
上面我們已經說過了磁碟 IO, 接下來我們談談網路 IO!
網路 IO 其實也是非常相似的。通過乙太網介面讀寫資料時也會造成阻塞,10G頻寬會比1G頻寬的阻塞耗時少一些,而 1G 頻寬又會比 100M 頻寬的阻塞少一些。通常情況下,我們把網路 IO 放在第三順位來考慮,然而有些人會在效能計算中忽略網路 IO 帶來的影響。
上圖是 PostgreSQL 的基準效能測試資料,從圖中我們可以看到,TPS 在連線數達到 50 時開始變緩。回過頭來想下,在上面 Oracle 的效能測試視訊中,測試人員們將連線數從 2048 降到了 96,實際上 96 還是太高了,除非你的伺服器 CPU 核心數有 16 或 32。
六、連線數計算公式
下面公式由 PostgreSQL 提供,不過底層原理是不變的,它適用於市面上絕大部分資料庫產品。還有,你應該模擬預期的訪問量,並通過下面的公式先設定一個偏合理的值,然後在實際的測試中,通過微調,來尋找最合適的連線數大小。
連線數 = ((核心數 * 2) + 有效磁碟數)
核心數不應包含超執行緒(hyper thread),即使開啟了超執行緒也是如此,如果熱點資料全被快取了,那麼有效磁碟數實際是0,隨著快取命中率的下降,有效磁碟數也逐漸趨近於實際的磁碟數。另外需要注意,這一公式作用於SSD 的效果如何,尚未明瞭。
好了,按照這個公式,如果說你的伺服器 CPU 是 4核 i7 的,連線池大小應該為 ((4 * 2) + 1) = 9
。
取個整, 我們就設定為 10 吧。你這個行不行啊?10 也太小了吧!
你要是覺得不太行的話,可以跑個效能測試看看,我們可以保證,它能輕鬆支撐 3000 使用者以 6000 TPS 的速率併發執行簡單查詢的場景。你還可以將連線池大小超過 10,那時,你會看到響應時長開始增加,TPS 開始下降。
七、結論:你需要的是一個小連線池,和一個等待連線的執行緒佇列
假設說你有 10000 個併發訪問,而你設定了連線池大小為 10000,你怕是石樂志哦。
改成 1000,太高?改成 100?還是太多了。
你僅僅需要一個大小為 10 資料庫連線池,然後讓剩下的業務執行緒都在佇列裡等待就可以了。
連線池中的連線數量大小應該設定成:資料庫能夠有效同時進行的查詢任務數(通常情況下來說不會高於 2*CPU核心數)。
你應該經常會看到一些使用者量不是很大的 web 應用中,為應付大約十來個的併發,卻將資料庫連線池設定成 100, 200 的情況。請不要過度配置您的資料庫連線池的大小。
八、額外需要注意的點
實際上,連線池的大小的設定還是要結合實際的業務場景來說事。
比如說,你的系統同時混合了長事務和短事務,這時,根據上面的公式來計算就很難辦了。正確的做法應該是建立兩個連線池,一個服務於長事務,一個服務於"實時"查詢,也就是短事務。
還有一種情況,比方說一個系統執行一個任務佇列,業務上要求同一時間內只允許執行一定數量的任務,這時,我們就應該讓併發任務數去適配連線池連線數,而不是連線數大小去適配併發任務數。
Ref
免費分享 | 面試&學習福利資源
最近在網上發現一個不錯的 PDF 資源《Java 核心知識&面試.pdf》分享給大家,不光是面試,學習,你都值得擁有!!!
獲取方式: 關注公眾號: 小哈學Java, 後臺回覆資源,既可免費無套路獲取資源連結,下面是目錄以及部分截圖:
重要的事情說兩遍,關注公眾號: 小哈學Java, 後臺回覆資源,既可免費無套路獲取資源連結 !!!