歡迎光臨
每天分享高質量文章

大話資料庫連線池簡史,你都用過幾個?

點選上方“芋道原始碼”,選擇“設為星標

做積極的人,而不是積極廢人!

原始碼精品專欄

 

來源:http://t.cn/EVA2ozi

  • 前言
  • 第一代連線池
    • 已經徹底死掉的c3p0和proxool
    • 鹹魚翻身的dbcp
    • 甘心赴死的BoneCP
  • 站在巨人肩膀上的第二代連線池
    • 效能無敵的HikariCP
    • 功能全面的druid
  • 最後,隱身的連線池

前言

資料庫連線池在Java資料庫相關中介軟體產品群中,應該算是底層最基礎的一類產品,作為企業應用開發必不可少的元件,無數天才們為我們貢獻了一個又一個的優秀產品,它們有的隨時代發展,功成身退,有的則還在不斷迭代,老而彌堅,更有新生代產品,或效能無敵,或功能全面。接下來,就讓我們好好聊聊,“那些年,我們用過的資料庫連線池”。

第一代連線池

區分一個資料庫連線池是屬於第一代產品還是第二代產品的一個最重要特徵就是看它在架構和設計時採用的執行緒模型,因為這直接影響的併發環境下存取資料庫連線的效能。一般來講採用單執行緒同步的架構設計的都屬於第一代連線池,而採用多執行緒非同步架構的則屬於第二代。比較有代表性的就是Apache Commons DBCP,在1.x版本中,一直延續這單執行緒設計模型,到2.x版本才採用多執行緒模型。

用版本釋出時間來辨別區分兩代產品,則一個偷懶的好方法。以下是這些常見資料庫連線池最新版本的釋出時間:

資料庫連線池 最新版本 釋出時間
c3p0 c3p0-0.9.5.2 on 9 Dec 2015
proxool 0.9.x May 24, 2011
dbcp 2.1.1 2015-08-06
BoneCP 0.8.0 on 23 Oct 2013
TJP tomcat9 Jan 10 2017
druid 1.0.28 2017-02-05
hikariCP 2.4.11 2017-01-28

從表中我們可以看到,c3p0、DBCP、Proxool和BoneCP都已經很久沒更新了,TJP(Tomcat JDBC Pool),druid,hikariCPze則仍處於活躍的更新中,後者明顯就是我們所說的二代產品了。

已經徹底死掉的c3p0和proxool

我對c3p0還是很有感情的,因為在它是我使用的第一款資料庫連線池,在很長一段時間內,它一直是Java領域內資料庫連線池的代名詞,當年盛極一時的Hibernate都將其作為內建的資料庫連線池,可見業內對它的穩定行還是認可的。關於c3p0如何使用,可以藉助於搜尋引擎,這裡就不再贅述了。c3p0功能簡單易用,穩定性好這是它的優點,但效能上的缺點卻讓它徹底被打入冷宮。c3p0的效能很差,差到即便是和同時代的產品相比,它也是墊底的(見圖一)。同時代的BoneCP更是直接以幹掉它為自己的口號(官網號稱比c3p0快25倍),更不要說和後來的druid和HikariCP相比了。

正常來講,有問題很正常,改就是了,但c3p0最致命的問題就是架構設計過於複雜,讓重構變成了一項不可能完成的任務。隨著國內網際網路大潮的湧起,效能有硬傷的c3p0徹底的退出了歷史舞臺。

如果說c3p0被人嫌棄,是因為它自身架構設計的“原罪”,那proxool的冷門,則是與作者興趣的缺失有關。proxool最初在設計上另闢蹊徑,以JDBC驅動的身份為使用者提供連線池服務,這使得將proxool移植到現有程式碼中別的十分容易,而且proxool還開創性的提供了連線池監控功能,讓它迅速的獲得了不少使用者的青睞。

但產品作者興趣的缺失,讓這款本來很有潛力的產品早早夭折。在github的專案首頁,作者寫到:“我從2006年之後就再沒碰過這個專案了,我甚至練Java都不用了…”,也許,proxool本來就是這位天才coder的練手之作,java本身也不是他的主力語言,但不論哪種原因,proxool都已經和c3p0一樣,鮮有人問津了。

This project is no longer actively maintained. I haven’t used Proxool myself since 2006 and no longer even use Java. If the project has any chance of survival at all it would need to find a new maintainer. If anyone can recommend a good alternative I would be happy to mention it here. Alternatively, if anyone wants to contribute to this project then please create a pull request.

圖一:第一代連線池效能測試

鹹魚翻身的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

可以看到,因為核心功能依賴於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重煥青春,雖然和新一代的連線池相比仍有一定差距,但差距並不大,dbcp 2.x版本已經穩穩達到了和新一代產品同級別的效能指標(見下圖)。

圖二:DBCP 2.x與新一代產品的效能對比

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

甘心赴死的BoneCP

在討論BoneCP這塊的內容之前,我們還是先來看看BoneCP作者自己是這麼評價這款產品的:

BoneCP is a Java JDBC connection pool implementation that is tuned for high performance by minimizing lock contention to give greater throughput for your applications. It beats older connection pools such as C3P0 and DBCP but should now be considered deprecated in favour of HikariCP.

用我自己的話翻譯一下就是:俺是一個高效能的資料庫連線池,俺之所以這麼牛逼是因為俺在實現的時候減少了鎖的使用,想當年,什麼c3p0啊DBCP啊都被老子乾趴了,但是現在為了支援HikariCP,俺選擇退出!也就是說,BoneCP的退出是它自己的選擇,但它又不像proxool是被拋棄的,它是作者經過深思熟慮後,做出的選擇,可以說BoneCP是“甘心赴死,殺身成仁”。那麼問題來了,BoneCP究竟是不是像它自己形容的那樣牛逼?BoneCP和HikariCP之間究竟有啥聯絡,能引得它主動“金盆洗手”?

先說效能,BoneCP自稱效能是c3p0的25倍,並提供了依照自己定義的測試案例,提供了一組圖片

  1. 單執行緒(1,000,000獲得及釋放資料庫連線請求,連線池大小20-50)

    單執行緒
  2. 多執行緒(500執行緒分別獲取釋放100個連結,連線池大小50-200)

    多執行緒1
  3. 多執行緒(500個執行緒每個100次獲得/釋放,連線池大小20-500)

    多執行緒2

當然,以上圖片僅供參考,因為不同的引數配置,不同的應用環境,不同的測試案例,得到的結果肯定也不會相同,官網提供的資料,肯定是在最有利於自己表現的環境下得到的。但結合另外一份測試資料(第一幅圖),可以看到BoneCP的效能在第一代產品中,確實是屬於領先地位的。高效能的表現的秘訣也並不高深,一是極簡的設計,整個產品只有幾百k大小,二是重構內部pool的設計,減少鎖的使用,而這兩點最佳化原則,幾乎適用於所以的連線池產品。

值得一提的是,BoneCP本身並不“健全”,它的很多特徵都依賴於Guava,因此也就和dbcp一樣,面臨更新乏力的問題。但現在,這些問題都不重要了,因為它賴以為傲的效能被HikariCP全面超越。HikariCP可以說是BoneCP的二代產品(HikariCP自己在官網上聲稱在BoneCP的基礎上,做了很多最佳化),它在設計思路上和BoneCP完全一致,主打的特徵也是超強的效能表現,關於HikariCP的詳細內容,我將在下一章節介紹。

站在巨人肩膀上的第二代連線池

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

  • HikariCP
  • druid

效能無敵的HikariCP

剛剛在介紹BoneCP的時候多少已經提到過HikariCP了,作為連線池產品中的“效能殺手”,它的表現究竟如何呢,先來看下官網提供的資料:

圖三:HikariCP官網配圖1

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

圖四:HikariCP官方配圖2

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

  • 位元組碼精簡:最佳化程式碼,直到編譯後的位元組碼最少,這樣,CPU快取可以載入更多的程式程式碼;
  • 最佳化代理和攔截器:減少程式碼,例如HikariCP的Statement proxy只有100行程式碼,只有BoneCP的十分之一;
  • 自定義陣列型別(FastStatementList)代替ArrayList:避免每次get()呼叫都要進行range check,避免呼叫remove()時的從頭到尾的掃描;
  • 自定義集合型別(ConcurrentBag):提高併發讀寫的效率;
  • 其他針對BoneCP缺陷的最佳化,比如對於耗時超過一個CPU時間片的方法呼叫的研究(但沒說具體怎麼最佳化)。

可以看到,上述這幾點最佳化,針對的都是BoneCP現有的缺陷,最佳化到這份上,也難怪BoneCP的作者不想玩了。綜合現在能找到的資料來看,HakariCP在效能上的優勢應該是得到共識的,再加上它自身小巧的身形,在當前的“雲時代、微服務”的背景下,HakariCP一定會得到更多人的青睞。

功能全面的druid

近幾年,阿裡在開源專案上動作頻頻,除了有像fastJson這類工具型專案,更有像AliSQL這類的大型軟體,今天說的druid,就是阿裡眾多優秀開源專案中的一個。它除了提供效能卓越的連線池功能外,還集成了sql監控,黑名單攔截等功能,用它自己的話說,druid是“為監控而生”。藉助於阿裡這個平臺的號召力,產品一經釋出就贏得了大批使用者的擁躉,從使用者使用的反饋來看,druid也確實沒讓使用者失望。

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

下圖為druid自己提供的效能測試資料:

圖五:druid提供的效能測試資料

最後,隱身的連線池

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

與我而言,連線池是我開啟自己技術棧(Java儲存層相關)的起點,它有著作為起點的一切優點:使用廣泛,入門簡單,同時核心思想和實踐又足夠有料。本篇文章僅是一個開端,既是幫助我(和大家)理清楚市面上這些產品的現狀,同時也為為接下來馬上開始的“深入研究”鋪點底子。如果一定要說點什麼感悟的話,那我只能再一次感嘆開源的力量以及社群對Java技術推廣的巨大助力。開源是一種精神,分享是一種態度,而這,正是Java語言幾十年依舊屹立不倒的原因。



歡迎加入我的知識星球,一起探討架構,交流原始碼。加入方式,長按下方二維碼噢

已在知識星球更新原始碼解析如下:

如果你喜歡這篇文章,喜歡,轉發。

生活很美好,明天見(。・ω・。)ノ♡

    閱讀原文

    贊(0)

    分享創造快樂