概述
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();
小結
從復雜邏輯中把連接池的相關邏輯抽取出來,其實就很簡單,類似於一個生產者消費者模型。希望的這篇文章對你有幫助。