1. 程式人生 > >你真的知道如何設定資料庫連線池的大小嗎

你真的知道如何設定資料庫連線池的大小嗎

前段時間在一個老專案中經歷過一個問題:一個 Dubbo 服務,啟動的時候慢的要死,後來看日誌查原因整個過程一直在初始化資料庫連線。一看資料庫連線引數,連線池大小:1024。

很多入行晚的同學沒有經歷過手寫 JDBC 連線的日子。那個時候沒有資料庫連線池的概念,都是原生程式碼一頓搞,後來有了 iBATIS 之後 Java 開發的繁雜程度才逐漸減輕,也衍生 C3P0 資料庫連線池這種基礎的東西。羅馬不是一天建成的,可是網際網路發展太快了,技術壓力逼迫下各種中介軟體被迫研發,大家加班加點搞出來各種高大上的腳手架,也成就很多 偉人

資料庫連線使用 TCP 的方式,建立連線需要3次握手,釋放連線需要4次揮手,當今這種網際網路使用頻率下,如果每一次訪問資料庫都重新建立連線,我估計你們公司倒閉800次都不夠。

1. 資料庫連線的過程是怎樣的

Java 鼻祖 Sun 公司是想以一套API統一天下,奈何各個資料庫伺服器廠商太給力統一不了。無奈之舉是建立了一個統一的介面,提出一套統一接入的步驟,各個廠商實現介面,按照步驟載入自己的資料庫。所以現在的方案就是4板斧:

  1. 註冊驅動,為人所知的:Class.forName()
  2. 獲取Connection,成功即與資料庫建立連線;
  3. 拿到Statement物件,用於操作資料庫的CRUD;
  4. 獲取資料庫返回結果ResultSet。

大家應該都知道資料庫本身是一個客戶端程式,只有啟動了才能連線。拿 MYSQL 舉例,我們在安裝並啟動了服務的機器上,命令列的方式輸入:mysql -uroot -p

即可連線當前資料庫。

MYSQL 連線方式有很多種,區分Unix系統 和 Windows 系統以及通用的連線方式,在這裡僅說兩種方式:一種為 unix domain socket,另外一種為基於 tcp/ip 協議,一般我們如果遠端訪問資料庫肯定是基於 tcp/ip 的,但是如果我們在本機登入就會分為使用 socket 還是 tcp/ip。

socket:mysql -uroot -p
tcp/ip:mysql -h127.0.0.1 -uroot -p

當資料庫伺服器和應用伺服器位於不同的主機時就要使用 tcp/ip 的方式建立連線。每一個連線在作業系統中佔用一個執行緒來維護。建立連線也分為兩類:短連線和長連線。

短連線

所謂短連線就是指應用程式和資料庫通訊完畢之後連線關閉。這種連線每次的操作就是:

發出請求--->建立連線--->操作資料--->釋放連線

這樣做的問題是:

  1. 頻繁的建立 / 釋放連線對資料庫來說增加了系統負擔;
  2. 應用程式每次操作資料庫的過程將會變得很慢;
  3. 應用系統每次建立連線都要佔用一個埠,頻繁的建立/釋放,每個被釋放的連線在發出釋放請求之後並不是馬上就執行,必須經歷一個 FIN 階段的等待直到確認為止。所以在每秒幾千次資料庫請求的時候,應用伺服器埠很有可能被消耗完。

長連線

長連線即在建立連線後一直開啟,直到應用程式關閉才釋放。使用長連線的好處是減少每次建立連線帶來的開銷。

對於應用伺服器來說維持長連線的好處不言自明,但是對於資料庫伺服器來說,過多的長連線則是災難。

MYSQL的TCP連線支援長連線,所以每次操作完資料庫,可以不必直接關掉連線,而是等待下次使用的時候在複用這個連線。所有的Socket長連線都是通過TCP自帶的ping來維持心跳(TCP保活),從而保持連線狀態,而我們熟悉的websocket,也正是通過TCP的心跳來維持連線不被中斷。

連線池

長連線的好處這麼大,自然大家都用長連線。慢慢就搞出一套長連線維護的工具 - 資料庫連線池。

設計連線池也沒有多麼複雜,大致的步驟就是:

  1. 初始化連線;
  2. 業務取出連線;
  3. 業務傳送請求;
  4. 放回連線。

除了上面的基本功能以外,還要處理併發問題,多資料庫伺服器和多使用者,事務處理,連線池的配置與維護。大概就這些功能。有了連線池之後,連線的建立和釋放跟業務就沒有關係,交給交接池來維護。

2. MYSQL 能支援多少連線

MYSQL 的最大連線數在5.7版本中預設是151, 最大可以達到16384(2^14)。如何設定最大連線數在於你的伺服器效能,檢視 MYSQL連線數資訊命令如下:

mysql> show variables like '%max_connections%';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 5050  |
+-----------------+-------+
1 row in set (0.00 sec)

我們生產環境MYSQL的最大連線數設定為 5050,注意不能設定的太小,太小造成的後果是連線失敗:“query failed Error 1040: Too many connections“ 錯誤。太大且當連線該資料庫的機器比較多的時候則會對當前MYSQL的效能產生影響。

MYSQL官網給出了一個設定最大連線數的建議比例:

Max_used_connections / max_connections * 100% ≈ 85%

即已使用的連線數佔總上限的85%左右,如果目前已使用的連線數與最大連線數比例小於10%那很顯然設定的過大。

查詢當前資料庫已建立連線數:

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 89    |
+-------------------+-------+
1 row in set (0.00 sec)

Mysql的配置可以在全域性變數中查詢和設定,相關的配置主要可以查詢下面這些:

配置 含義
Connections 嘗試連線Mysql的連線數,不管連線成功與否,該值都會+1
Threads_connected 已經建立的連線數,單節點下一般小於最大連線池最大連線數
max_connections Mysql限制的最大的可連線的數量
wait_timeout 即MYSQL長連線(非互動式)的最大生命時長,預設是8小時
interactive_timeout 長連線(互動式)的最大生命時長,預設是8小時

3. 連線池設定多少連線才合適

設定連線池的大小肯定不是越大越好,需要考慮的是當前服務所在機器的效能,網路狀況,資料庫機器效能,資料庫特性等等。同時也要做到不浪費系統資源,記憶體,埠,同步訊號量等等。

比如說應用伺服器Tomcat設定的最大執行緒池預設值200,最大假設每個執行緒會用到一個數據庫連線,那麼執行緒池大小應該小於等於200。

另外需要考慮的是,每申請一個長連線都會在物理網路上建立一個用於長連線維護的程序,而程序的執行跟物理機的CPU核數有關。理論上一個8核的伺服器將連線池設定為8最佳,每一個核同時處理一個執行緒,超過8的併發就有執行緒上下文切換的開銷。

這裡有一個 Oracle 效能小組釋出的簡短視訊,連線池測試分2個部分:測試視訊1 ,測試視訊2 。視訊中調整了執行緒池大小為2048的時候資料庫效能陡然下降,後面調整到144就恢復了。PostgreSQL提供了一個設定預期執行緒池大小的公式:

connections = ((core_count * 2) + effective_spindle_count)

該公式來自於:https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing。

其中,core_count是CPU核心, effective_spindle_count 的含義是有效主軸數,如果你的伺服器使用的是帶有16個磁碟的RAID,那麼valid_spindle_count=16。它實質上是伺服器可以管理多少個並行I / O請求的度量。旋轉硬碟一次(通常)一次只能處理一個I / O請求,如果你有16個,則系統可以同時處理16個I / O請求。

我想 Hikari 作為目前最優秀的資料庫連線池之一,提出的這個公式還是經得起檢驗的。大家不妨在生產環境試試(出問題別找我)。