數據庫連接池的大小你真的設置對了嗎

問題

真實環境prod中的系統,我們該如何設置數據庫連接池的大小呢?

一些所謂的開發老鳥可能會肯定的告訴你:沒關係,儘量設置的大些,比如設置成200,這樣數據庫性能會高些,吞吐量也會大些!

對於菜鳥的你,也許認爲好像似乎說的有道理,真的是這樣嗎?接下來的分析,也許顛覆你的認知哦!

數據庫連接池的設置分析——測試數據

條件 線程池設置大小 每個請求在連接池隊列裏平均等待時間 執行SQL耗時 總耗時
Oracle、9600併發線程、每次數據庫操作sleep 550ms 2048 33ms 77ms 110ms
Oracle、9600併發線程、每次數據庫操作sleep 550ms 1024 33ms 56ms 89ms
Oracle、9600併發線程、每次數據庫操作sleep 550ms 96 1ms 2ms 3ms

該測試數據是Oracle 性能小組測試&發佈的,視頻內容在https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
我把視頻描述的實驗條件和結果彙總成此表了。

連接池設置數量越小、總耗時越少?

測試結果:只是將數據庫連接池的大小降低了一下,這樣,就能把之前平均100ms響應時間縮短到了3ms。吞吐量指數級上升啊!!!神奇!!!

爲啥有這種效果呢?
看到結果,感到震驚。那我們坐下來想想:
爲啥Nginx內部僅僅使用了4個線程,其性能就大大超越了100個進程的Apache HTTPD呢?

要能弄懂這麼難的問題,我們得補習下基礎:

程序、進程、線程的區別?

程序(Program) 是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念——它是在磁盤上

進程(Process) 是程序在處理機上的一次執行過程,它是一個動態的概念。——是在內存中

線程(Thread) 是程序中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位,指運行中的程序的調度單位。

進程:作爲資源分配的單元
線程:作爲調度和執行的單位

這裏有個小結論:在一核 CPU 的機器上,順序執行A和B永遠比通過時間分片切換“同時”執行A和B要快。

時間片的概念是什麼?

時間片即CPU分配給各個程序的時間,每個線程被分配一個時間段, 稱作它的時間片,即該進程允許運行的時間,使各個程序從表面上看是同時進行的。

如果在時間片結束時進程還在運行,則CPU將被剝奪並分配給另一個進程。如果進程在時間片結束前阻塞或結束,則CPU當即進行切換。而不會造成CPU資源浪費。

在宏觀上:我們可以同時打開多個應用程序,每個程序並行不悖,同時運行。

但在微觀上:由於只有一個CPU,一次只能處理程序運行的一部分,如何處理公平,一種方法就是引入時間片,每個程序輪流執行。

結論:當線程數大於CPU的核心數,需要通過算法及時間分片切換該哪個線程獲得CPU的使用權,這個是需要耗時的。因爲這裏涉及到上下文切換耗費的額外的性能。

如果計算機是4核,機器值運行四個線程,處理速度相當快。但這只是理想狀態。

限制因素

實際上,限制線程發揮的,不止有CPU,還有磁盤IO和網絡IO。
下面分別來看CPU 磁盤IO 網絡IO

只考慮CPU

結論1:如果只考慮CPU,不考慮磁盤I/O和網絡I/O,在一個8核的服務器上,數據庫連接數/線程數設置爲8能夠提供最優的性能,如果再增加連接數,反而會因爲上下文切換導致性能下降。(這是理想情況)
數據庫連接數/線程數= CPU的核心數

考慮磁盤I/O

耗時因素:尋址的耗時+旋轉耗時;

當你的線程處理的是IO密集型業務時,便可以讓 線程/連接數 設置的比CPU核心數大一些,這樣就能夠在同樣的時間內,完成更多的工作,提升吞吐量。因爲磁盤尋址的時候,CPU是空閒的,可以讓CPU做點其他的。

問題:設置成多大合適呢?
取決於磁盤:SSD固態硬盤、普通機械硬盤
SSD固態硬盤,它不需要尋址,也不需要旋轉碟片!是不是可以設置更大些?

結論正好相反! 無需尋址和沒有旋迴耗時的確意味着更少的阻塞,所以更少的線程( 更接近於CPU核心數)會發揮出更高的性能。只有當阻塞密集時,更多的線程數才能發揮出更好的性能。

考慮網絡IO

網絡是我們應用程序最難把控了,因爲網絡擁塞、超時 由不得我們控制;通常網絡瓶頸業務我們系統優化放在最後的。

如圖,隨着客戶端連接數的增加,每秒傳輸速度影響還是很大的。
該圖是 PostgreSQL 的基準性能測試數據,從圖中我們可以看到,TPS 在連接數達到 50 時開始變緩。回過頭來想下,在上面 Oracle 的性能測試視頻中,測試人員們將連接數從 2048 降到了 96,實際上 96 還是太高了,除非你的服務器 CPU 核心數有 16 或 32。

數據庫連接池計算公式

下面公式由PostgreSQL 提供,不過底層原理是不變的,它適用於市面上絕大部分數據庫產品。還有,你應該模擬預期的訪問量,並通過下面的公式先設置一個偏合理的值,然後在實際的測試中,通過微調,來尋找最合適的連接數大小。

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

注意:核心數不應包含超線程(hyper thread),即使打開了超線程也是如此,如果熱點數據全被緩存了,那麼有效磁盤數實際是0,隨着緩存命中率的下降,有效磁盤數也逐漸趨近於實際的磁盤數。另外需要注意,這一公式作用於SSD的效果如何,尚未明瞭。

舉個栗子
按照這個公式,如果說你的服務器CPU是4核i7的,連接池大小應該爲((4*2)+1)=9。
取個整,我們就設置爲10吧。你這個行不行啊? 10 也太小了吧!
測試結果表明:這個設置能輕鬆支撐3000用戶以6000 TPS的速率併發執行簡單查詢的場景。你還可以將連接池大小超過10,那時,你會看到響應時長開始增加,TPS開始下降。

結論:你需要的是一個小連接池,和一個等待連接的線程隊列!
補充:連接池中的連接數量大小應該設置成:數據庫能夠有效同時進行的查詢任務數(通常情況下來說不會高於2*CPU核心數)。

補充

(公式並不是適用所有場景的,具體還需看場景)

實際上,連接池的大小的設置還是要結合實際的業務場景來說事。
比如說,你的系統同時混合了長事務短事務,這時,根據上面的公式來計算就很難辦了。正確的做法應該是創建兩個連接池,一個服務於長事務,一個服務於"實時"查詢,也就是短事務。
還有一種情況,比方說一個系統執行一個任務隊列,業務上要求同一時間內只允許執行一定數量的任務,這時,我們就應該讓併發任務數去適配連接池連接數,而不是連接數大小去適配併發任務數。
記住:讓業務併發數,去適配數據庫連接數。

回顧數據庫連接池的實現原理

JDBC訪問數據庫的問題總結
(1)數據庫連接資源很稀缺,每次創建和關閉都非常耗時。
(2)每次數據庫連接使用完後要及時關閉回收,如果程序運行中間出現異常,導致連接沒有及時關閉,將有可能導致內存泄漏,服務器崩潰。
(3)這種方式,不能控制被創建的連接對象的數量,也就是說,你可以創建無數個連接對象。沒有誰去約束限制,對象太多,導致內存泄漏,服務器崩潰。

後記

到這裏,是否顛覆了你的認知呢?

今天爲什麼要探究數據庫連接池大小呢?是因爲本人所在的公司(某大廠),負責的某服務數據庫連接池最大配置的是20,但系統平時限流設置的是100併發/s,當時看到時愣住了~
後來查看了另外一個系統,日調用量8k萬,分庫分表了,QPS 900多,數據庫連接池最大配置是64。
怎麼樣,有點驚訝吧。
還有同學測試的:“我試過600連接數和200連接數,壓測性能相差不大”。
因此數據庫連接池大小,絕非越大越好,也不是max pool size一定要跟隨系統併發量的增加而增加。我們需要做的是讓業務併發數,去適配數據庫連接數;讓我們的數據庫能夠有效的同時進行併發執行、處於高效處理的狀態。

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