摘要: 本文非原創,是筆者搜集了一些HikariCP相關的資料整理給大家的介紹,主要講解了為什麼sb2選擇了HikariCP以及HikariCP為什麼這麼快。
Springboot2預設資料庫連接配接池選擇了HikariCP為何選擇HikariCP理由一、代碼量理由二、口碑理由三、速度理由四、穩定性理由五、可靠性HikariCP為什麼這麼快優化并精簡位元組碼更好的并發集合類實作使用FastList替代ArrayListHikariCP與Druid相比哪個更好?Springboot2快速上手參考資料
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#Spring-Boot-2%E9%BB%98%E8%AE%A4%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0%E9%80%89%E6%8B%A9%E4%BA%86HikariCP Spring Boot 2預設資料庫連接配接池選擇了HikariCP
預設的資料庫連接配接池由Tomcat換成HikariCP. 如果在一個Tomcat應用中用spring.datasource.type來強制使用Hikari連接配接池, 則可以去掉這個override.
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#%E4%B8%BA%E4%BD%95%E9%80%89%E6%8B%A9HikariCP 為何選擇HikariCP
HiKariCP是資料庫連接配接池的一個後起之秀,号稱性能最好,可以完美地PK掉其他連接配接池,是一個高性能的JDBC連接配接池,基于BoneCP做了不少的改進和優化。其作者還有另外一個開源作品——高性能的JSON解析器HikariJSON。
它,超快,快到連Spring Boot 2都宣布支援了。
代碼體積更是少的可憐,130kb。
https://github.com/brettwooldridge/HikariJSON
為何要使用HiKariCP?這要先從BoneCP說起:
什麼?不是有C3P0/DBCP這些成熟的資料庫連接配接池嗎?一直用的好好的,為什麼又搞出一個BoneCP來?因為,傳說中BoneCP在快速這個特點上做到了極緻,官方資料是C3P0等的25倍左右。不相信?其實我也不怎麼信。可是,有圖有真相啊(圖檔來自BoneCP官網:
http://jolbox.com/benchmarks.html):
從上述結果可以看出HikariCP的性能遠高于c3p0、tomcat等連接配接池,以緻後來BoneCP作者都放棄了維護,在Github項目首頁推薦大家使用HikariCP。另外,Spring Boot将在2.0版本中把HikariCP作為其預設的JDBC連接配接池。
PS:需要指出的是,上圖中的資料是HikariCP作者對各個連接配接池調用DataSource.getConnection()、Connection.close()、Connection.prepareStatement()、Statement.execute()、Statement.close()方法的性能測試結果。
而且,網上對于BoneCP是好評如潮啊,推薦的文章一搜一大堆。
然而,上Maven Repository網站(
http://mvnrepository.com/artifact/com.jolbox/bonecp
)查找有沒有最新版本的時候,你會發現最新的是2013年10月份的(這麼久沒新版本出來了?)。于是,再去BoneCP的Githut(
https://github.com/wwadge/bonecp
)上看看最近有沒有送出代碼。卻發現,BoneCP的作者對于這個項目貌似已經心灰意冷,說是要讓步給HikariCP了(有圖有真相):
……什麼?又來一個CP?……什麼是Hikari?
Hikari來自日文,是“光”(陽光的光,不是光秃秃的光)的意思。作者估計是為了借助這個詞來暗示這個CP速度飛快。不知作者是不是日本人,不過日本也有很多優秀的碼農,聽說比特币據說日本人搞出來的。。。
這個産品的口号是“快速、簡單、可靠”。實際情況跟這個口号真的比對嗎?又是有圖有真相(Benchmarks又來了):
這個圖,也間接地、再一次地證明了boneCP比c3p0強大很多,當然,跟“光”比起來,又弱了不少啊。
那麼,這麼好的是怎麼做到的呢?官網詳細地說明了HikariCP所做的一些優化,總結如下:
- 位元組碼精簡 :優化代碼,直到編譯後的位元組碼最少,這樣,CPU緩存可以加載更多的程式代碼;
- 優化代理和攔截器:減少代碼,例如HikariCP的Statement proxy隻有100行代碼,隻有BoneCP的十分之一;
- 自定義數組類型(FastStatementList)代替ArrayList:避免每次get()調用都要進行range check,避免調用remove()時的從頭到尾的掃描;
- 自定義集合類型(ConcurrentBag:提高并發讀寫的效率;
- 其他針對BoneCP缺陷的優化,比如對于耗時超過一個CPU時間片的方法調用的研究(但沒說具體怎麼優化)。
很多優化的對比都是針對BoneCP的……哈哈。參考文章:
https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Holehttps://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#%E7%90%86%E7%94%B1%E4%B8%80%E3%80%81%E4%BB%A3%E7%A0%81%E9%87%8F 理由一、代碼量
幾個連接配接池的代碼量對比(代碼量越少,一般意味着執行效率越高、發生bug的可能性越低):
理由二、口碑
可是,“黃婆賣瓜,自催自擂”這個俗語日本人也是懂得,于是,使用者的好評如潮也是有圖有真相
理由三、速度
還有第三方關于速度的測試:
理由四、穩定性
也許你會說,速度高,如果不穩定也是硬傷啊。于是,關于穩定性的圖也來了:
理由五、可靠性
另外,關于可靠性方面,也是有實驗和資料支援的。對于資料庫連接配接中斷的情況,通過測試getConnection(),各種CP的不相同處理方法如下:
(所有CP都配置了跟connectionTimeout類似的參數為5秒鐘)
- HikariCP:等待5秒鐘後,如果連接配接還是沒有恢複,則抛出一個SQLExceptions 異常;後續的getConnection()也是一樣處理;
- C3P0:完全沒有反應,沒有提示,也不會在“CheckoutTimeout”配置的時長逾時後有任何通知給調用者;然後等待2分鐘後終于醒來了,傳回一個error;
- Tomcat:傳回一個connection,然後……調用者如果利用這個無效的connection執行SQL語句……結果可想而知;大約55秒之後終于醒來了,這時候的getConnection()終于可以傳回一個error,但沒有等待參數配置的5秒鐘,而是立即傳回error;
- BoneCP:跟Tomcat的處理方法一樣;也是大約55秒之後才醒來,有了正常的反應,并且終于會等待5秒鐘之後傳回error了;
可見,HikariCP的處理方式是最合理的。根據這個測試結果,對于各個CP處理資料庫中斷的情況,評分如下:
參考文章:
https://github.com/brettwooldridge/HikariCP/wiki/Bad-Behavior:-Handling-Database-Downhttps://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#HikariCP%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E4%B9%88%E5%BF%AB HikariCP為什麼這麼快
JDBC連接配接池的實作并不複雜,主要是對JDBC中幾個核心對象Connection、Statement、PreparedStatement、CallableStatement以及ResultSet的封裝與動态代理。接下來從幾個方面來看看HikariCP為什麼這麼快:
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#%E4%BC%98%E5%8C%96%E5%B9%B6%E7%B2%BE%E7%AE%80%E5%AD%97%E8%8A%82%E7%A0%81 優化并精簡位元組碼
HikariCP利用了一個第三方的Java位元組碼修改類庫Javassist來生成委托實作動态代理。動态代理的實作在ProxyFactory類,源碼如下:
發現這些代理方法中隻有一行直接抛異常的代碼,注釋寫着“Body is replaced (injected) by JavassistProxyFactory”,其實方法body中的代碼是在編譯時調用JavassistProxyFactory才生成的,主要代碼見下圖:
之是以使用Javassist生成動态代理,是因為其速度更快,相比于JDK Proxy生成的位元組碼更少,精簡了很多不必要的位元組碼。
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#ConcurrentBag%EF%BC%9A%E6%9B%B4%E5%A5%BD%E7%9A%84%E5%B9%B6%E5%8F%91%E9%9B%86%E5%90%88%E7%B1%BB%E5%AE%9E%E7%8E%B0 ConcurrentBag:更好的并發集合類實作
ConcurrentBag的實作借鑒于C#中的同名類,是一個專門為連接配接池設計的lock-less集合,實作了比LinkedBlockingQueue、LinkedTransferQueue更好的并發性能。ConcurrentBag内部同時使用了ThreadLocal和CopyOnWriteArrayList來存儲元素,其中CopyOnWriteArrayList是線程共享的。ConcurrentBag采用了queue-stealing的機制擷取元素:首先嘗試從ThreadLocal中擷取屬于目前線程的元素來避免鎖競争,如果沒有可用元素則再次從共享的CopyOnWriteArrayList中擷取。此外,ThreadLocal和CopyOnWriteArrayList在ConcurrentBag中都是成員變量,線程間不共享,避免了僞共享(false sharing)的發生。
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#%E4%BD%BF%E7%94%A8FastList%E6%9B%BF%E4%BB%A3ArrayList 使用FastList替代ArrayList
FastList是一個List接口的精簡實作,隻實作了接口中必要的幾個方法。JDK ArrayList每次調用get()方法時都會進行rangeCheck檢查索引是否越界,FastList的實作中去除了這一檢查,隻要保證索引合法那麼rangeCheck就成為了不必要的計算開銷(當然開銷極小)。此外,HikariCP使用List來儲存打開的Statement,當Statement關閉或Connection關閉時需要将對應的Statement從List中移除。通常情況下,同一個Connection建立了多個Statement時,後打開的Statement會先關閉。ArrayList的remove(Object)方法是從頭開始周遊數組,而FastList是從數組的尾部開始周遊,是以更為高效。
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#HikariCP%E4%B8%8EDruid%E7%9B%B8%E6%AF%94%E5%93%AA%E4%B8%AA%E6%9B%B4%E5%A5%BD%EF%BC%9F HikariCP與Druid相比哪個更好?
有些使用者給了druid這樣的評論:
不評論,一個追求性能,一個偏向監控,直接看之前有人給HikariCP提的關于跟Druid對比分析的issue吧。HikariCP作者對Druid做了測試并給出了測試結果資料,Druid作者溫少也對此作了評論。Issue連結:
https://github.com/brettwooldridge/HikariCP/issues/232
筆者個人的觀點是,hikariCP可以提供監控功能的,比如metrics,可以參見筆者的這篇文章
【追光者系列】HikariCP連接配接池監控名額實戰。
另外,監控方面,skywalking、pinpoint、mycat這些agent也是可以做到的,以後service mesh普及了更加可以監控了,比如sharding-jdbc也可以做監控,datamesh,sidecar也可以做監控的。
https://blog.didispace.com/Springboot-2-0-HikariCP-default-reason/#Springboot2%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B Springboot2快速上手
說得這麼好,用起來會不會很麻煩啊,會不會有很多參數要配置才能有這樣的效果啊?答案是:不會。
springboot 2.0 預設連接配接池就是Hikari了,是以引用parents後不用專門加依賴
配置一下就好
# jdbc_config datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/datebook?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=root
# Hikari will use the above plus the following to setup connection pooling
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
直接啟動即可 如圖