穩穩的掌握「數據庫鏈接池

摘要

如何打造高性能的數據庫鏈接池框架,能夠從哪些角度進行優化,鏈接池的大量優化實踐如何爲你的系統保駕護航,本專題將帶你走進鏈接池的世界,爲你一一揭曉。java

你們可能會有這樣疑問:鏈接池相似於線程池或者對象池,就是一個放鏈接的池子,使用的時候從裏面拿一個,用完了再歸還,功能很是簡單,有什麼可講的。數據庫

可能還會有這樣的疑問:高性能這麼高大上,一個小小的鏈接池,如何跟高大上靠上邊的。緩存

本主題將會全面介紹鏈接池原理,高性能的設計,優化實踐,現有鏈接池的瓶頸及解決方案。同時也會介紹惟品會自研數據庫鏈接池產品(代號:Caelus)性能優化

爲何要有鏈接池

先看一下鏈接池所處的位置:服務器

應用框架的業務實現通常都會訪問數據庫,緩存或者HTTP服務。爲何要在訪問的地方加上一個鏈接池呢?網絡

下面以訪問MySQL爲例,執行一個SQL命令,若是不使用鏈接池,須要通過哪些流程。併發

1:TCP創建鏈接的三次握手框架

2:MySQL認證的三次握手異步

3:真正的SQL執行分佈式

4:MySQL的關閉

5:TCP的四次握手關閉

能夠看到,爲了執行一條SQL,卻多了很是多咱們不關心的網絡交互。

優勢:實現簡單。

缺點

1:網絡IO較多

2:數據庫的負載較高

3:響應時間較長及QPS較低

4:應用頻繁的建立鏈接和關閉鏈接,致使臨時對象較多,GC頻繁

5:在關閉鏈接後,會出現大量TIME_WAIT 的TCP狀態(在2個MSL以後關閉)

使用鏈接池流程

第一次訪問的時候,須要創建鏈接。 可是以後的訪問,均會複用以前建立的鏈接。

優勢

1:較少了網絡開銷

2:系統的性能會有一個實質的提高

3:沒了麻煩的TIME_WAIT狀態

固然,現實每每是殘酷的,當咱們解決了一個問題的時候,同時伴隨着另一個問題的產生。

使用鏈接池面臨的最大挑戰: 鏈接池的性能

鏈接數和線程數性能優化

分庫DB部署結構

假設有128個分庫:32個服務器,每一個服務器有4個schema。按照128個分庫的設計,便會新建128個獨立數據庫鏈接池。

數據庫鏈接池的模型

特色

1:128個鏈接池徹底獨立,不一樣的schema也對應不一樣的鏈接池

2:先經過拆庫,讀寫等策略選擇對應的鏈接池,再從鏈接池獲取一個鏈接進行操做

3:操做完後,再將鏈接歸還到對應的鏈接池中。

優勢

結構簡單,分散競爭

面臨的問題:

1:線程數過多

先看一下新建一個鏈接池,須要新建的線程數的個數。

鏈接池 線程數 描述 128個分庫須要的線程數
C3P0 4 3個helperThread (pollerThread),1個定時任務AdminTaskTimer(DeadlockDetector) 4*128=512
DBCP 1 負責心跳,最小鏈接數維持,最大空閒時間和防鏈接泄露 1*128=128
Druid 2 一個異步建立鏈接。一個異步關閉鏈接。 2*128=256

能夠看到隨着分庫的增長,無論選用哪一個鏈接池,線程的個數均會線性增加。線程數過多將會致使內存佔用較大: 默認1個線程會佔用1M的空間,若是是512個線程,則會佔用1M*512=512M上下文切換開銷。

Tips:因爲stack和heap申請爲虛地址空間,可是一旦使用就不會釋放。(線程也不必定會佔用1M的空間)

2:鏈接數過多

數據庫的鏈接資源比較重,而且隨着鏈接的增長,數據庫的性能會有明顯的降低。DBA通常會限制每一個DB創建鏈接的個數,好比限制爲3K 。假設數據庫單臺限制3K,32臺則容量爲3K*32=96K。若是應用最大,最小鏈接數均爲10,則每一個應用總計須要128*10=1.28K個鏈接。那麼數據庫理論上支持的應用個數爲96K/1.28K= 80 臺

3:不能鏈接複用

同一個物理機下面不一樣的schema徹底獨立,鏈接不能複用

優化後的數據庫鏈接池模型

特色:

1:只有一個鏈接池,全部節點共享線程 (解決了線程數過多的問題)

2:每一個物理機對應一個host, host裏面維護多個schema,schema存放鏈接。

3:同一個host下面的不一樣schema 能夠進行鏈接複用(解決鏈接數過多的問題)

獲取鏈接流程:

1:獲取鏈接須要帶上 ip,port和schema信息:好比獲取的是host31的schema1

2:先到host31的schema1中獲取空閒鏈接,可是schema1無空閒鏈接,便會從schema2中獲取空閒鏈接。

3:從schema2中獲取的鏈接執行useschema1,該鏈接便切換到schema1上面。

4:執行對應的SQL操做,執行完成後,歸還鏈接到schema1的池子裏面。

優勢:

1:鏈接複用:有效減小鏈接數。

2:提高性能:避免頻繁的新建鏈接。新建鏈接的開銷比較大,而使用use schema開銷很是小

3:有效減小線程數。按現有方案大概只須要4個線程便可。而優化前須要512個線程

缺點:

1:管理較爲複雜

2:不符合JDBC接口規範。DataSource只有簡單的getConnection()接口,沒有針對獲取對應schema的鏈接的接口。須要繼承DataSouce,實現特定接口。

事務語句性能優化

優化前執行事務的模型

從鏈接池裏面獲取到鏈接,默認是自動提交。爲了開啓事務,須要執行setautocommit=false 操做,而後再執行具體的SQL,歸還鏈接的時候,還須要將鏈接設置爲自動提交(須要執行set autocommit=true) 。能夠看到開啓事務,須要額外執行兩條事務的語句。

優化後執行事務的模型

每一個schema裏面全部的鏈接會按照autocommit進行分組。 分爲自動提交(autocommit=true) 和非自動提交(autocommit=false)。獲取鏈接時優先獲取相同autocommit的分組裏的鏈接,若是沒有可用鏈接則從另一個分組中獲取鏈接,業務操做執行完後,再歸還到對應的分組裏面。該種機制避免了開啓事務多執行的兩條事務語句。

鎖性能優化

鏈接池的通用功能:

鏈接池主要包含五部分:獲取鏈接,歸還鏈接,定時任務,維護組件及資源池

獲取鏈接:

1:獲取超時:若是超過規定時間未獲取到鏈接,則會拋出異常

2:有效性檢查:當從資源池裏面獲取到資源,須要檢查該資源的有效性,若是失效,再次獲取鏈接。避免執行業務的時候報錯。

3:建立鏈接:能夠同步建立,也能夠異步建立。

歸還鏈接:

1:歸還鏈接:好比須要檢查最大空閒數,肯定是物理關閉仍是歸還到鏈接池

2:銷燬鏈接: 可同步銷燬也可異步銷燬

定時任務:

1:空閒檢查:主要是檢查空閒鏈接,鏈接空閒超過必定時間,則會關閉鏈接。

2:最小鏈接數控制:通常會設置最小鏈接數。保證當前系統裏面最小的鏈接數。若是不夠,則會新建鏈接。

組件維護:

1:鏈接狀態控制:空閒,使用,刪除等狀態控制

2:異常處理:對JDBC訪問的異常統一處理,若是異常與鏈接相關,則會將該鏈接銷燬掉。

3:緩存:避免對SQL重複解析,PrepareStatement機制下,會對SQL解析的對象進行緩存。

4:JDBC封裝:對JDBC進行了實現,真正的實現是底層的driver,好比MySQL-connector-java 。

資源池:

1:資源池是存放鏈接的地方,也是鏈接池最核心的地方。

2:全部的組件基本上都與資源池進行交互,對鏈接資源的競爭很是激烈。該處的性能將決定了整個鏈接池的性能。

3:通常資源池的實現是使用JDK提供的BlockingQueue。那麼是否有方案能夠進行無鎖的設計,來避免競爭。

資源池無鎖設計

獲取鏈接大概流程:

1:從ThreadLocal裏面獲取鏈接,若是沒有空閒鏈接,則從全局鏈接池(CopyOnWriteArrayList)中獲取。

2:若是全局鏈接池中沒有空閒鏈接,則會異步新建鏈接。

3:斷定超時時間是否大於閾值,若是小於閾值,則進行自旋。不然進行park休眠。

4:鏈接創建成功後,會對park的線程進行喚醒

主要從四個方面實現了無鎖的設計:ThreadLocal,CopyOnWriteArrayList,異步創建鏈接及自旋。

ThreadLocal

1:每一個線程均有一個鏈接隊列。該隊列是全局隊列的引用。

2:獲取鏈接時先從ThreadLocal裏面拿鏈接,若是鏈接是空閒狀態,則使用。不然移除掉,再拿下一個,直到拿不到鏈接爲止。

3:歸還鏈接時,只須要歸還到Threadlocal的隊列裏面,同時設置鏈接爲空閒狀態

4:若是使用BlockQueue,獲取鏈接時調用poll,歸還鏈接時調用offer,存在兩次鎖的競爭。優化後經過CAS避免了兩次鎖的開銷(獲取鏈接時,使用CAS置鏈接爲非空閒狀態;歸還時,使用CAS置鏈接爲空閒狀態)

CopyOnWriteArrayList

1:該隊列使用場景是:大量讀,少許寫的操做,而且存儲的數據比較有限。而鏈接池的場景很是適合採用CopyOnWriteArrayList。

2:在獲取鏈接或者歸還鏈接時,只會經過CAS更改鏈接的狀態,不會對鏈接池進行添加或者刪除的操做。

3:通常鏈接池鏈接的個數比較可控,CopyOnWriteArrayList在寫操做時會對全部鏈接進行拷貝,對內存影響不大。

異步創建鏈接

獲取到鏈接後,判斷一下是否有併發正在等待獲取鏈接,若是有,則異步創建鏈接。避免下一個鏈接的等待。若是CopyOnWriteArrayList沒有空閒鏈接,則異步創建鏈接。

自旋

該自旋比較相似於JDK對synchronized的自旋機制。若是發現超時時間大於設定的閾值(好比10微秒),則會進行線程掛起。若是小於設定的閾值,則從新獲取鏈接,進行自選,避免線程的上下文切換帶來的性能開銷。。

優化小技巧

方法內聯優化

1:每調用一次方法,線程便會新建一個棧幀,新建棧幀開銷相對比較大

2:JIT在運行時會進行內聯優化,多個方法使用一個棧幀,避免棧幀新建過多

3:JIT方法內聯優化默認的字節碼個數閾值是35個字節,低於35個字節,纔會進行優化。(可經過-XX:MaxInlineSize=35進行設置)

經過修改上述代碼,編譯後字節碼修改到34個字節,則能夠知足內聯的條件。

心跳語句選擇

PrepareStatement模式選擇

MySQL driver默認是client模式,若是須要開啓server模式,須要設置 useServerPrepStmts=true 。PrepareStatement默認的client模式和Statement對於DB端沒有區別。你們廣泛理解PrepareStatement和Statement的區別是PrepareStatement能夠避免SQL注入。可是避免SQL注入是如何作到的?

使用PrepareStatement設置參數的時候,好比調用setString(int parameterIndex, String x),本地會對設置的參數進行轉義來避免SQL注入。

執行SQL的時候,會將SQL的?替換成轉義後的字符,發送到數據庫執行。

PSCache

MySQLdriver 默認不開啓,可經過設置 cachePrepStmts = true 進行開啓

QueryTimeout

以前也遇到由於開啓了queryTimeout,致使鏈接泄露的問題。

惟品會自研鏈接池:Caelus

Caelus是惟品會自研的高性能的分佈式的數據庫鏈接池。

  • 高性能:基於無鎖的鏈接池設計模型來提高鏈接池性能;

  • 在分庫較多的場景下,減小線程數。 假若有128個分庫,現有鏈接池模型下則須要使用128個獨立的鏈接池,每一個鏈接池都須要線程(1-4個,不一樣的鏈接池不一樣)處理任務。則總共須要維護128到128*4個線程,開銷巨大。而Caelus鏈接池會大大減小線程數。

  • 鏈接複用。 對於 一個MySQL 的instance上面有多個schema場景下。現有鏈接池不一樣的schema的鏈接不可複用。而Caelus能夠複用不一樣schema的鏈接,提高性能。

  • 過多的事務指令。若是是事務語句,則從鏈接池拿到鏈接後,須要先開啓事務(setautocommit=false),歸還時須要再設置(set autocommit=true)。每使用一次鏈接,均須要額外執行兩條事務指令。Caelus能有效減小事務指令。

  • 配置規範的統一。結合MySQL的設置,提供規範統一,最優的配置。 

相關文章
相關標籤/搜索