rickiyang
http://www.javashuo.com/article/p-asotzhfm-bp.htmlhtml
前段時間在一個老項目中經歷過一個問題:一個 Dubbo 服務,啓動的時候慢的要死,後來看日誌查緣由整個過程一直在初始化數據庫鏈接。一看數據庫鏈接參數,鏈接池大小:1024。mysql
不少入行晚的同窗沒有經歷過手寫 JDBC 鏈接的日子。那個時候沒有數據庫鏈接池的概念,都是原生代碼一頓搞,後來有了 iBATIS 以後 Java 開發的繁雜程度才逐漸減輕,也衍生 C3P0 數據庫鏈接池這種基礎的東西。羅馬不是一天建成的,但是互聯網發展太快了,技術壓力逼迫下各類中間件被迫研發,你們加班加點搞出來各類高大上的腳手架,也成就不少 偉人。git
數據庫鏈接使用 TCP 的方式,創建鏈接須要3次握手,釋放鏈接須要4次揮手,當今這種互聯網使用頻率下,若是每一次訪問數據庫都從新創建鏈接,我估計大家公司倒閉800次都不夠。github
1. 數據庫鏈接的過程是怎樣的
Java 鼻祖 Sun 公司是想以一套API統一天下,奈何各個數據庫服務器廠商太給力統一不了。無奈之舉是建立了一個統一的接口,提出一套統一接入的步驟,各個廠商實現接口,按照步驟加載本身的數據庫。因此如今的方案就是4板斧:web
- 註冊驅動,爲人所知的:
Class.forName()
; - 獲取Connection,成功即與數據庫創建鏈接;
- 拿到Statement對象,用於操做數據庫的CRUD;
- 獲取數據庫返回結果ResultSet。
你們應該都知道數據庫自己是一個客戶端程序,只有啓動了才能鏈接。拿 MYSQL 舉例,咱們在安裝並啓動了服務的機器上,命令行的方式輸入:mysql -uroot -p
便可鏈接當前數據庫。sql
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 的方式創建鏈接。每個鏈接在操做系統中佔用一個線程來維護。創建鏈接也分爲兩類:短鏈接和長鏈接。服務器
短鏈接
所謂短鏈接就是指應用程序和數據庫通訊完畢以後鏈接關閉。這種鏈接每次的操做就是:websocket
發出請求--->創建鏈接--->操做數據--->釋放鏈接
這樣作的問題是:網絡
- 頻繁的創建 / 釋放鏈接對數據庫來講增長了系統負擔;
- 應用程序每次操做數據庫的過程將會變得很慢;
- 應用系統每次創建鏈接都要佔用一個端口,頻繁的創建/釋放,每一個被釋放的鏈接在發出釋放請求以後並非立刻就執行,必須經歷一個 FIN 階段的等待直到確認爲止。因此在每秒幾千次數據庫請求的時候,應用服務器端口頗有可能被消耗完。
長鏈接
長鏈接即在創建鏈接後一直打開,直到應用程序關閉才釋放。使用長鏈接的好處是減小每次建立鏈接帶來的開銷。
對於應用服務器來講維持長鏈接的好處不言自明,可是對於數據庫服務器來講,過多的長鏈接則是災難。
MYSQL的TCP鏈接支持長鏈接,因此每次操做完數據庫,能夠沒必要直接關掉鏈接,而是等待下次使用的時候在複用這個鏈接。全部的Socket長鏈接都是經過TCP自帶的ping來維持心跳(TCP保活),從而保持鏈接狀態,而咱們熟悉的websocket
,也正是經過TCP的心跳來維持鏈接不被中斷。
鏈接池
長鏈接的好處這麼大,天然你們都用長鏈接。慢慢就搞出一套長鏈接維護的工具 - 數據庫鏈接池。
設計鏈接池也沒有多麼複雜,大體的步驟就是:
- 初始化鏈接;
- 業務取出鏈接;
- 業務發送請求;
- 放回鏈接。
除了上面的基本功能之外,還要處理併發問題,多數據庫服務器和多用戶,事務處理,鏈接池的配置與維護。大概就這些功能。有了鏈接池以後,鏈接的創建和釋放跟業務就沒有關係,交給交接池來維護。
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的配置能夠在全局變量中查詢和設置,相關的配置主要能夠查詢下面這些:
3. 鏈接池設置多少鏈接才合適
設置鏈接池的大小確定不是越大越好,須要考慮的是當前服務所在機器的性能,網絡情況,數據庫機器性能,數據庫特性等等。同時也要作到不浪費系統資源,內存,端口,同步信號量等等。
好比說應用服務器Tomcat設置的最大線程池缺省值200,最大假設每一個線程會用到一個數據庫鏈接,那麼線程池大小應該小於等於200。
另外須要考慮的是,每申請一個長鏈接都會在物理網絡上創建一個用於長鏈接維護的進程,而進程的執行跟物理機的CPU核數有關。理論上一個8核的服務器將鏈接池設置爲8最佳,每個核同時處理一個線程,超過8的併發就有線程上下文切換的開銷。
這裏有一個 Oracle 性能小組發佈的簡短視頻,鏈接池測試分2個部分:
測試視頻1
https://apexapps.oracle.com/pls/apex/f?p=44785:112:11510768535381::::P112_CONTENT_ID:9565
測試視頻2
https://apexapps.oracle.com/pls/apex/f?p=44785:112:11510768535381::::P112_CONTENT_ID:9566
視頻中調整了線程池大小爲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 做爲目前最優秀的數據庫鏈接池之一,提出的這個公式仍是經得起檢驗的。你們不妨在生產環境試試(出問題別找我)。