1. 程式人生 > >資料庫連線池學習筆記(一):原理介紹+常用連線池介紹

資料庫連線池學習筆記(一):原理介紹+常用連線池介紹

什麼是連線池

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

為什麼要使用連線池

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

傳統的連線機制與資料庫連線池的執行機制區別

不使用連線池流程

下面以訪問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