基于HiKariCP组件,分析连接池原理
池塘里養:Connection;
一、設計與原理
1、基礎案例
HiKariCP作為SpringBoot2框架的默認連接池,號稱是跑的最快的連接池,數據庫連接池與之前兩篇提到的線程池和對象池,從設計的原理上都是基于池化思想,只是在實現方式上有各自的特點;首先還是看HiKariCP用法的基礎案例:
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement;public class ConPool {private static HikariConfig buildConfig (){HikariConfig hikariConfig = new HikariConfig() ;// 基礎配置hikariConfig.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/junit_test?characterEncoding=utf8");hikariConfig.setUsername("root");hikariConfig.setPassword("123456");// 連接池配置hikariConfig.setPoolName("dev-hikari-pool");hikariConfig.setMinimumIdle(4);hikariConfig.setMaximumPoolSize(8);hikariConfig.setIdleTimeout(600000L);return hikariConfig ;}public static void main(String[] args) throws Exception {// 構建數據源HikariDataSource dataSource = new HikariDataSource(buildConfig()) ;// 獲取連接Connection connection = dataSource.getConnection() ;// 聲明SQL執行Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery("SELECT count(1) num FROM jt_activity") ;// 輸出執行結果if (resultSet.next()) {System.out.println("query-count-result:"+resultSet.getInt("num"));}} }2、核心相關類
- HikariDataSource類:匯集數據源描述的相關信息,例如配置、連接池、連接對象、狀態管理等;
- HikariConfig類:維護數據源的配置管理,以及參數校驗,例如userName、passWord、minIdle、maxPoolSize等;
- HikariPool類:提供對連接池與池中對象管理的核心能力,并實現池相關監控數據的查詢方法;
- ConcurrentBag類:拋棄了常規池中采用的阻塞隊列作為容器的方式,自定義該并發容器來存儲連接對象;
- PoolEntry類:拓展連接對象的信息,例如狀態、時間等,方便容器中追蹤這些實例化對象;
通過對連接池中幾個核心類的分析,也能直觀地體會到該源碼的設計原理,與上篇總結的對象池應用有異曲同工之妙,只是不同的組件不同的開發者在實現的時候,都具備各自的抽象邏輯。
3、加載邏輯
通過配置信息去構建數據源描述,在構造方法中基于配置再去實例化連接池,在HikariPool的構造中,實例化ConcurrentBag容器對象;下面再從源碼層面分析實現細節。
二、容器分析
1、容器結構
容器ConcurrentBag類提供PoolEntry類型的連接對象存儲,以及基本的元素管理能力,對象的狀態描述;雖然被HikariPool對象池類所持有,但是實際的操作邏輯是在該類中;
1.1 基礎屬性
其中最為核心的是sharedList共享集合、threadList線程級緩存、handoffQueue即時隊列;
// 共享對象集合,存放數據庫連接 private final CopyOnWriteArrayList<T> sharedList; // 緩存線程級連接對象,會被優先使用,避免被爭搶 private final ThreadLocal<List<Object>> threadList; // 等待獲取連接的線程數 private final AtomicInteger waiters; // 標記是否關閉 private volatile boolean closed; // 即時處理連接的隊列,當有等待線程時,通過該隊列將連接分配給等待線程 private final SynchronousQueue<T> handoffQueue;1.2 狀態描述
在ConcurrentBag類中的IConcurrentBagEntry內部接口,被PoolEntry類實現,該接口定義連接對象的狀態:
- STATE_NOT_IN_USE:未使用,即閑置中;
- STATE_IN_USE:使用中;
- STATE_REMOVED:被廢棄;
- STATE_RESERVED:保留態,中間狀態,用于嘗試驅逐連接對象時;
2、包裝對象
容器的基本能力是用來存儲連接對象的,而對象的管理則需要很多擴展的跟蹤信息,以有效的完成各種場景下的識別,此時就需要借助包裝類的引入;
// 業務真正使用的連接對象 Connection connection; // 最近訪問時間 long lastAccessed; // 最近借出時間 long lastBorrowed; // 狀態描述 private volatile int state = 0; // 是否驅逐 private volatile boolean evict; // 生命周期結束時的調度任務 private volatile ScheduledFuture<?> endOfLife; // 連接生成的Statement對象 private final FastList<Statement> openStatements; // 池對象 private final HikariPool hikariPool;這里需要注意FastList類實現List接口,為HiKariCP組件自定義,相比ArrayList類,出于對性能的追求,在元素的管理時,去掉諸多的范圍校驗。
三、對象管理
基于連接池的常規用法,來看看連接對象具體是如何管理,比如被借出,被釋放,被廢棄等,以及這些操作下對象的狀態轉換過程;
1、初始化
上文加載邏輯的描述中,已經提到在構建數據源的時候,會根據配置實例化連接池,在初始化的時候,基于兩個核心切入點來分析源碼:1.實例化多少連接對象、2.連接對象轉換包裝對象;
在連接池的構造中執行了checkFailFast方法,在該方法內執行MinIdle最小空閑數的判斷,如果大于0,則創建一個包裝對象并放入容器中;
public HikariPool(final HikariConfig config) ; private void checkFailFast() {final PoolEntry poolEntry = createPoolEntry();if (config.getMinimumIdle() > 0) {connectionBag.add(poolEntry);} }需要注意兩個問題,創建的連接包裝對象,初始狀態是0即閑置中;另外雖然案例中設置MinIdle=4的值,但是這里的判斷大于0,也只在容器中預先放入一個空閑對象;
2、借用對象
從池中獲取連接對象時,實際調用的是容器類中的borrow方法:
public Connection HikariPool.getConnection(final long hardTimeout) throws SQLException ; public T ConcurrentBag.borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException ;在執行borrow方法時,涉及如下幾個核心步驟與邏輯:
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {// 遍歷本地線程緩存final List<Object> list = threadList.get();for (int i = list.size() - 1; i >= 0; i--) {final Object entry = list.remove(i);final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { }}// 增加等待線程數final int waiting = waiters.incrementAndGet();try {// 遍歷Shared共享集合for (T bagEntry : sharedList) {if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { }}// 一定時間內輪詢handoff隊列listener.addBagItem(waiting);timeout = timeUnit.toNanos(timeout);do {final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);} } finally {// 減少等待線程數waiters.decrementAndGet();} }- 首先反向遍歷本地線程緩存,如果存在空閑連接,則返回該對象;如果沒有則尋找共享集合;
- 遍歷Shared共享集合前,會標記等待線程數加1,如果存在空閑連接則直接返回;
- 當Shared共享集合中也沒有空閑連接時,這時當前線程進行一定時間的handoffQueue隊列輪詢,可能會有資源的釋放,也可能是新添加的資源;
注意這里在遍歷集合時,取出的對象都會對狀態進行判斷和更新,如果得到空閑對象,會更新為IN_USE狀態,然后返回;
3、釋放對象
從池中釋放連接對象時,實際調用的是容器類中的requite方法:
void HikariPool.recycle(final PoolEntry poolEntry) ; public void ConcurrentBag.requite(final T bagEntry) ;在釋放連接對象時,首先更新對象狀態為空閑,然后判斷當前是否有等待的線程,在borrow方法中等待線程會進入一定時間的輪詢,如果沒有的話則把對象放入本地線程緩存中:
public void requite(final T bagEntry) {// 更新狀態bagEntry.setState(STATE_NOT_IN_USE);// 等待線程判斷for (int i = 0; waiters.get() > 0; i++) {if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) { }}// 本地線程緩存final List<Object> threadLocalList = threadList.get();if (threadLocalList.size() < 50) {threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);} }注意這里涉及到連接對象的狀態從使用中轉為NOT_IN_USE空閑;borrow與requite作為連接池中兩個核心方法,負責資源創建與回收;
最后本篇文章并沒有站在HiKariCP組件的整體設計上構思,只是分析連接池這冰山一角,盡管只是部分源碼,但是已經足夠彰顯出作者對于性能的極致追求,比如:本地線程緩存、自定義容器類型、FastList等;能被普遍采用必然存在諸多支撐的理由。
《End》
總結
以上是生活随笔為你收集整理的基于HiKariCP组件,分析连接池原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 田志刚:致《你的知识需要管理》读者
- 下一篇: Delphi-TScreen表示应用程序