使用數據庫連接池連接數據庫 數據庫連接池學習筆記(一):原理介紹+常用連接池介紹

數據庫連接池學習筆記(一):原理介紹+常用連接池介紹

數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重複使用一個現有的數據庫連接,而不是再重新建立一個。

爲什麼要使用連接池

 數據庫連接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現得尤爲突出。  一個數據庫連接對象均對應一個物理數據庫連接,每次操作都打開一個物理連接,使用完都關閉連接,這樣造成系統的 性能低下。 數據庫連接池的解決方案是在應用程序啓動時建立足夠的數據庫連接,並講這些連接組成一個連接池(簡單說:在一個“池”裏放了好多半成品的數據庫聯接對象),由應用程序動態地對池中的連接進行申請、使用和釋放。對於多於連接池中連接數的併發請求,應該在請求隊列中排隊等待。並且應用程序可以根據池中連接的使用率,動態增加或減少池中的連接數。 連接池技術儘可能多地重用了消耗內存地資源,大大節省了內存,提高了服務器地服務效率,能夠支持更多的客戶服務。通過使用連接池,將大大提高程序運行效率,同時,我們可以通過其自身的管理機制來監視數據庫連接的數量、使用情況等。 

傳統的連接機制與數據庫連接池的運行機制區別

不使用連接池流程

下面以訪問MySQL爲例,執行一個SQL命令,如果不使用連接池,需要經過哪些流程。

不使用數據庫連接池的步驟:

  1. TCP建立連接的三次握手
  2. MySQL認證的三次握手
  3. 真正的SQL執行
  4. MySQL的關閉
  5. TCP的四次握手關閉

可以看到,爲了執行一條SQL,卻多了非常多我們不關心的網絡交互。

優點:

  1. 實現簡單

缺點:

  1. 網絡IO較多
  2. 數據庫的負載較高
  3. 響應時間較長及QPS較低
  4. 應用頻繁的創建連接和關閉連接,導致臨時對象較多,GC頻繁
  5. 在關閉連接後,會出現大量TIME_WAIT 的TCP狀態(在2個MSL之後關閉)

使用連接池流程

使用數據庫連接池的步驟:

第一次訪問的時候,需要建立連接。 但是之後的訪問,均會複用之前創建的連接,直接執行SQL語句。

優點:

  1. 較少了網絡開銷
  2. 系統的性能會有一個實質的提升
  3. 沒了麻煩的TIME_WAIT狀態

數據庫連接池的工作原理

連接池的工作原理主要由三部分組成,分別爲

  1. 連接池的建立
  2. 連接池中連接的使用管理
  3. 連接池的關閉

        第一、連接池的建立。一般在系統初始化時,連接池會根據系統配置建立,並在池中創建了幾個連接對象,以便使用時能從連接池中獲取。連接池中的連接不能隨意創建和關閉,這樣避免了連接隨意建立和關閉造成的系統開銷。Java中提供了很多容器類可以方便的構建連接池,例如Vector、Stack等。

        第二、連接池的管理。連接池管理策略是連接池機制的核心,連接池內連接的分配和釋放對系統的性能有很大的影響。其管理策略是:

        當客戶請求數據庫連接時,首先查看連接池中是否有空閒連接,如果存在空閒連接,則將連接分配給客戶使用;如果沒有空閒連接,則查看當前所開的連接數是否已經達到最大連接數,如果沒達到就重新創建一個連接給請求的客戶;如果達到就按設定的最大等待時間進行等待,如果超出最大等待時間,則拋出異常給客戶。

        當客戶釋放數據庫連接時,先判斷該連接的引用次數是否超過了規定值,如果超過就從連接池中刪除該連接,否則保留爲其他客戶服務。

        該策略保證了數據庫連接的有效複用,避免頻繁的建立、釋放連接所帶來的系統資源開銷。

        第三、連接池的關閉。當應用程序退出時,關閉連接池中所有的連接,釋放連接池相關的資源,該過程正好與創建相反。

連接池主要參數

使用連接池時,要配置一下參數

  1. 最小連接數:是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費.
  2. 最大連接數:是連接池能申請的最大連接數,如果數據庫連接請求超過次數,後面的數據庫連接請求將被加入到等待隊列中,這會影響以後的數據庫操作
  3. 最大空閒時間
  4. 獲取連接超時時間
  5. 超時重試連接次數

連接池需要注意的點

1、併發問題

  爲了使連接管理服務具有最大的通用性,必須考慮多線程環境,即併發問題。這個問題相對比較好解決,因爲各個語言自身提供了對併發管理的支持像java,c#等等,使用synchronized(java)lock(C#)關鍵字即可確保線程是同步的。使用方法可以參考,相關文獻。

2、事務處理

  我們知道,事務具有原子性,此時要求對數據庫的操作符合“ALL-OR-NOTHING”原則,即對於一組SQL語句要麼全做,要麼全不做。 
  我們知道當2個線程共用一個連接Connection對象,而且各自都有自己的事務要處理時候,對於連接池是一個很頭疼的問題,因爲即使Connection類提供了相應的事務支持,可是我們仍然不能確定那個數據庫操作是對應那個事務的,這是由於我們有2個線程都在進行事務操作而引起的。爲此我們可以使用每一個事務獨佔一個連接來實現,雖然這種方法有點浪費連接池資源但是可以大大降低事務管理的複雜性。 

3、連接池的分配與釋放

  連接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,可以提高連接的複用度,從而降低建立新連接的開銷,同時還可以加快用戶的訪問速度。 
  對於連接的管理可使用一個List。即把已經創建的連接都放入List中去統一管理。每當用戶請求一個連接時,系統檢查這個List中有沒有可以分配的連接。如果有就把那個最合適的連接分配給他(如何能找到最合適的連接文章將在關鍵議題中指出);如果沒有就拋出一個異常給用戶,List中連接是否可以被分配由一個線程來專門管理捎後我會介紹這個線程的具體實現。

4、連接池的配置與維護

  連接池中到底應該放置多少連接,才能使系統的性能最佳?系統可採取設置最小連接數(minConnection)和最大連接數(maxConnection)等參數來控制連接池中的連接。比方說,最小連接數是系統啓動時連接池所創建的連接數。如果創建過多,則系統啓動就慢,但創建後系統的響應速度會很快;如果創建過少,則系統啓動的很快,響應起來卻慢。這樣,可以在開發時,設置較小的最小連接數,開發起來會快,而在系統實際使用時設置較大的,因爲這樣對訪問客戶來說速度會快些。最大連接數是連接池中允許連接的最大數目,具體設置多少,要看系統的訪問量,可通過軟件需求上得到。 
  如何確保連接池中的最小連接數呢?有動態和靜態兩種策略。動態即每隔一定時間就對連接池進行檢測,如果發現連接數量小於最小連接數,則補充相應數量的新連接,以保證連接池的正常運轉。靜態是發現空閒連接不夠時再去檢查。

數據庫對比

第一、二代連接池

區分一個數據庫連接池是屬於第一代產品還是代二代產品有一個最重要的特徵就是看它在架構和設計時採用的線程模型,因爲這直接影響的是併發環境下存取數據庫連接的性能。

一般來講採用單線程同步的架構設計都屬於第一代連接池,二採用多線程異步架構的則屬於第二代。比較有代表性的就是Apache Commons DBCP,在1.x版本中,一直延續着單線程設計模式,到2.x才採用多線程模型。

用版本發佈時間來辨別區分兩代產品,則一個偷懶的好方法。以下是這些常見數據庫連接池最新版本的發佈時間:

數據庫連接池

最新版本

發佈時間

C3P0

c3p0-0.9.5.2

on 9 Dec 2015

DBCP

2.2.0

27 December 2017

Druid

0.11.0

Dec 4 2017

HikariCP

2.7.6

2018-01-14

從表中可以看出,C3P0已經很久沒有更新了。DBCP更新速度很慢,基本處於不活躍狀態,而Druid和HikariCP處於活躍狀態的更新中,這就是我們說的二代產品了。

二代產品對一代產品的超越是顛覆性的,除了一些“歷史原因”,你很難再找到第二條理由說服自己不選擇二代產品,但任何成功都不是偶然的,二代產品的成功很大程度上得益於前代產品們打下的基礎,站在巨人的肩膀上,新一代的連接池的設計師們將這一項“工具化”的產品,推向了極致。其中,最具代表性的兩款產品是:

  • HikariCP
  • Druid

徹底死掉的C3P0

C3P0是我使用的第一款數據庫連接池,在很長一段時間內,它一直是Java領域內數據庫連接池的代名詞,當年盛極一時的Hibernate都將其作爲內置的數據庫連接池,可以業內對它的穩定性還是認可的。C3P0功能簡單易用,穩定性好這是它的優點,但是性能上的缺點卻讓它徹底被打入冷宮。C3P0的性能很差,差到即便是同時代的產品相比它也是墊底的,更不用和Druid、HikariCP等相比了。正常來講,有問題很正常,改就是了,但c3p0最致命的問題就是架構設計過於複雜,讓重構變成了一項不可能完成的任務。隨着國內互聯網大潮的涌起,性能有硬傷的c3p0徹底的退出了歷史舞臺。

鹹魚翻身的DBCP

DBCP(DataBase Connection Pool)屬於Apache頂級項目Commons中的核心子項目(最早在Jakarta Commons裏就有),在Apache的生態圈中的影響裏十分廣泛,比如最爲大家所熟知的Tomcat就在內部集成了DBCP,實現JPA規範的OpenJPA,也是默認集成DBCP的。但DBCP並不是獨立實現連接池功能的,它內部依賴於Commons中的另一個子項目Pool,連接池最核心的“池”,就是由Pool組件提供的,因此,DBCP的性能實際上就是Pool的性能,DBCP和Pool的依賴關係如下表:

Apache Commons DBCP

Apache Commons Pool

v1.2.2

v1.3

v1.3

v1.5.4

v1.4

v1.5.4

v2.0.x

v2.2

v2.1.x

v2.4.2

v2.2.x

v2.5.0

可以看到,因爲核心功能依賴於Pool,所以DBCP本身只能做小版本的更新,真正大版本的更迭則完全依託於pool。有很長一段時間,pool都還是停留在1.x版本,這直接導致DBCP也更新乏力。很多依賴DBCP的應用在遇到性能瓶頸之後,別無選擇,只能將其替換掉,DBCP忠實的擁躉tomcat就在其tomcat 7.0版本中,自己重新設計開發出了一套連接池(Tomcat JDBC Pool)。好在,在2013年事情終於迎來轉機,13年9月Commons-Pool 2.0版本發佈,14年2月份,DBCP也終於迎來了自己的2.0版本,基於新的線程模型全新設計的“池”讓DBCP重煥青春,雖然和新一代的連接池相比仍有一定差距,但差距並不大,DBCP2.x版本已經穩穩達到了和新一代產品同級別的性能指標(見下圖)。

DBCP終於靠Pool鹹魚翻身,打了一個漂亮的翻身仗,但長時間的等待已經完全消磨了用戶的耐心,與新一代的產品項目相比,DBCP沒有任何優勢,試問,誰會在有選擇的前提下,去選擇那個並不優秀的呢?也許,現在還選擇DBCP2的唯一理由,就是情懷吧。

性能無敵的HikariCP

HikariCP號稱“性能殺手”(It’s Faster),它的表現究竟如何呢,先來看下官網提供的數據:

不光性能強勁,穩定性也不差:

那它是怎麼做到如此強勁的呢?官網給出的說明如下:

  • 字節碼精簡:優化代碼,直到編譯後的字節碼最少,這樣,CPU緩存可以加載更多的程序代碼;
  • 優化代理和攔截器:減少代碼,例如HikariCP的Statement proxy只有100行代碼;
  • 自定義數組類型(FastStatementList)代替ArrayList:避免每次get()調用都要進行range check,避免調用remove()時的從頭到尾的掃描;
  • 自定義集合類型(ConcurrentBag):提高併發讀寫的效率;
  • 其他缺陷的優化,比如對於耗時超過一個CPU時間片的方法調用的研究(但沒說具體怎麼優化)。

可以看到,上述這幾點優化,和現在能找到的資料來看,HakariCP在性能上的優勢應該是得到共識的,再加上它自身小巧的身形,在當前的“雲時代、微服務”的背景下,HakariCP一定會得到更多人的青睞。

功能全面的Druid

近幾年,阿里在開源項目上動作頻頻,除了有像fastJson、dubbo這類項目,更有像AliSQL這類的大型軟件,今天說的Druid,就是阿里衆多優秀開源項目中的一個。它除了提供性能卓越的連接池功能外,還集成了SQL監控,黑名單攔截等功能,用它自己的話說,Druid是“爲監控而生”。藉助於阿里這個平臺的號召力,產品一經發布就贏得了大批用戶的擁躉,從用戶使用的反饋來看,Druid也確實沒讓用戶失望。

相較於其他產品,Druid另一個比較大的優勢,就是中文文檔比較全面(畢竟是國人的項目麼),在github的wiki頁面,列舉了日常使用中可能遇到的問題,對一個新用戶來講,上面提供的內容已經足夠指導它完成產品的配置和使用了。

下圖爲Druid自己提供的性能測試數據:

現在項目開發中,我還是比較傾向於使用Durid,它不僅僅是一個數據庫連接池,它還包含一個ProxyDriver,一系列內置的JDBC組件庫,一個SQL Parser。

Druid 相對於其他數據庫連接池的優點

  1. 強大的監控特性,通過Druid提供的監控功能,可以清楚知道連接池和SQL的工作情況。

a. 監控SQL的執行時間、ResultSet持有時間、返回行數、更新行數、錯誤次數、錯誤堆棧信息;

b. SQL執行的耗時區間分佈。什麼是耗時區間分佈呢?比如說,某個SQL執行了1000次,其中0~1毫秒區間50次,1~10毫秒800次,10~100毫秒100次,100~1000毫秒30次,1~10秒15次,10秒以上5次。通過耗時區間分佈,能夠非常清楚知道SQL的執行耗時情況;

c. 監控連接池的物理連接創建和銷燬次數、邏輯連接的申請和關閉次數、非空等待次數、PSCache命中率等。

  1. 方便擴展。Druid提供了Filter-Chain模式的擴展API,可以自己編寫Filter攔截JDBC中的任何方法,可以在上面做任何事情,比如說性能監控、SQL審計、用戶名密碼加密、日誌等等。
  2. Druid集合了開源和商業數據庫連接池的優秀特性,並結合阿里巴巴大規模苛刻生產環境的使用經驗進行優化。

總結

時至今日,雖然每個應用(需要RDBMS的)都離不開連接池,但在實際使用的時候,連接池已經可以做到“隱形”了。也就是說在通常情況下,連接池完成項目初始化配置之後,就再不需要再做任何改動了。不論你是選擇Druid或是HikariCP,甚至是DBCP,它們都足夠穩定且高效!之前討論了很多關於連接池的性能的問題,但這些性能上的差異,是相較於其他連接池而言的,對整個系統應用來說,第二代連接池在使用過程中體會到的差別是微乎其微的,基本上不存在因爲連接池的自身的配飾和使用導致系統性能下降的情況,除非是在單點應用的數據庫負載足夠高的時候(壓力測試的時候),但即便是如此,通用的優化的方式也是單點改集羣,而不是在單點的連接池上死扣。

 

參考:

http://rainbowhorse.site/2018/02/06/%E5%A4%A7%E8%AF%9D%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/

https://blog.csdn.net/frightingforambition/article/details/25464129

https://juejin.im/entry/58fb03220ce4630061233c98

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