Druid源碼閱讀之連接池


概述

Druid是阿里巴巴開源的一個數據庫連接池 源碼地址。下面簡單分析一下連接池是怎么實現的

怎么開始閱讀

如果使用過Druid連接池的都只要在Spring配置中配置jdbc的時候配置Driver是使用的DruidDataSource。因此,在讀源碼的時候也可以從這個類入口。

Datasouce

什么是Datasouce呢,其實就是用於管理數據庫連接的工廠類。接口就2個方法

public interface DataSource  extends CommonDataSource, Wrapper {
  Connection getConnection() throws SQLException;
  Connection getConnection(String username, String password)
    throws SQLException;
}

DruidDataSource

DruidDataSource就是實現了這個接口,利用池化思想來管理數據庫連接。池化的思想我理解的主要有2個目的:

  • 一個目的是可以重復利用一些資源,特別是那些創建和銷毀的開銷都比較大的資源
  • 一個是可以控制資源的數量,防止大規模的創建導致系統問題 因此,DruidDataSource的關鍵就是在調用getConnection() 的時候從連接池中獲取正真的數據庫連接,並且在關閉連接的時候並不是真正的關閉物理連接,而是把連接重新放到連接池中。

創建連接池

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        init();

        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis);
        }
    }

init()就是初始化連接池,其中核心代碼:

public void init() throws SQLException {
    ...
    //line : 845
    for (int i = 0, size = getInitialSize(); i < size; ++i) {
        //看名字就是知道是保存物理連接的類。並且在這里會實際創建物理連接(JDBC的Connection),
//ps.准確的是說是ConnectionProxyImpl類,這個類是實現監控的關鍵,后面會再寫一篇文章介紹
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection(); //這種構造的時候傳入 this和另外一個對象一般情況都是包裝類, //這樣在DruidConnectionHolder就可以獲取DruidDataSource的一些狀態字段和成員對象(連接歸還的時候就會用到) DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo); //connections 保存連接的數組 connections[poolingCount] = holder; //方法就一行代碼:poolingCount++; 從屬性的名字推斷就是對連接池中的連接計數 //初始化完成首poolingCount的值就等於初始化連接的數量 incrementPoolingCount(); } ...

初始化完成后就看怎么獲取連接,回到上面getConnection()的方法中,直接看getConnectionDirect()方法吧。(我相信有過濾器的創建連接最終肯定還是調用這個方法,只不過這里會用到責任鏈模式來處理過濾器,可以參考之前的文章介紹責任鏈實現方式)。下面看看

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
    int notFullTimeoutRetryCnt = 0;
    for (;;) {
        DruidPooledConnection poolableConnection;
        try {
            poolableConnection = getConnectionInternal(maxWaitMillis);
        } catch (GetConnectionTimeoutException ex) {
           ...
        }

        //這里都是做一些配置的校驗,比如配置了testOnBorrow,那么在這里會對連接進行測試
        ...

        return poolableConnection;
    }
}

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
    DruidConnectionHolder holder;
    ...
    if (maxWait > 0) {
        holder = pollLast(nanos);
    } else {
        holder = takeLast();
    }

    if (holder != null) {
        activeCount++;
        if (activeCount > activePeak) {
            activePeak = activeCount;
            activePeakTime = System.currentTimeMillis();
        }
    }
   ...

    DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
    return poolalbeConnection;
}

這里看到DruidConnectionHolder,也就是再初始化的時候生成的包含了物理連接的保證類,那么pollLast(nanos)肯定就是有超時時間的獲取,takeLast()肯定就是無超時時間的獲取,那么直接看takeLast()

DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
        try {
            while (poolingCount == 0) {
                emptySignal(); // send signal to CreateThread create connection

                ...
                notEmpty.await(); // signal by recycle or creator
                ...
            }
        } catch (InterruptedException ie) {
            ...
        }
        //poolingCount--
        decrementPoolingCount();
        DruidConnectionHolder last = connections[poolingCount];
        connections[poolingCount] = null;

        return last;
    }

 

這里就是如何從連接池獲取連接的核心代碼了,這里poolingCount為0的情況下就會發送empty信號(回想一下自己剛開始寫生產者消費者的代碼吧,是不是用的到了一個empty和full來控制消費隊列為空和滿的情況),這里也是這樣的,當poolingCount==0的時候就表示沒有可用的連接。

  • 如果達到最大連接數,阻塞
  • 如果沒有達到,創建新的連接,這里創建新的連接是通過一個線程去執行的,詳情參考CreateConnectionTask。

當然如果當poolingCount不為0的時候,那么直接從連接數組中獲取下表為當poolingCount-1的連接返回就可以啦。

連接關閉

看了上面如何從連接池中獲取連接,那么很自然都可以知道如何把連接放回連接池中,肯定就是 connections[poolingCount] = 待返回的連接,然后poolingCount+1。

/**
 * 回收連接
 * DruidDataSouce.java
 */
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
    final DruidConnectionHolder holder = pooledConnection.getConnectionHolder();

    result = putLast(holder, lastActiveTimeMillis);
           ....
}

boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
    if (poolingCount >= maxActive) {
        return false;
    }

    e.setLastActiveTimeMillis(lastActiveTimeMillis);
    connections[poolingCount] = e;
    incrementPoolingCount();

    notEmpty.signal();
    return true;
}

果然和我們想的一樣,不過這里還有一個很重要的一部,調用notEmpty.signal();

小結

從復雜邏輯中把連接池的相關邏輯抽取出來,其實就很簡單,類似於一個生產者消費者模型。希望的這篇文章對你有幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM