druid 連接池源碼分析

一 java 對(duì)數(shù)據(jù)庫的支持

java.sql 包的支持,一般使用顯示編程的方式。 connection接口、statment接口、ResultSet接口、DriverManager類。

image.png

JDBC例子


 Connection con = null; //表示數(shù)據(jù)庫的連接對(duì)象 

  PreparedStatement pstmt = null; //表示數(shù)據(jù)庫更新操作 

  ResultSet result = null; 

  String like_name ="Tom1"; 

  intlike_age = 12; 

  String sql = "select name,age,birthday from java_study.person where name like ? or age = ?"; 

  Class.forName(DBDRIVER); //1、使用CLASS 類加載驅(qū)動(dòng)程序 

  System.out.println(sql); 

  con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、連接數(shù)據(jù)庫 

  pstmt = con.prepareStatement(sql); //使用預(yù)處理的方式創(chuàng)建對(duì)象 

  pstmt.setString(1, "%"+like_name+"%"); 

  pstmt.setInt(2, like_age); 

  result = pstmt.executeQuery(); //執(zhí)行SQL 語句,更新數(shù)據(jù)庫 

  while(result.next()){ 

      String name = result.getString("name"); 

      intage = result.getInt("age"); 

      Date date = result.getDate("birthday"); 

      System.out.println(name+","+age+","+date); 

  } 

          result.close();

         pstmt.close();

con.close(); // 4、關(guān)閉數(shù)據(jù)庫

JDBC事務(wù)例子

public void JdbcTransfer() { 
    java.sql.Connection conn = null;
     try{ 
        conn = conn =DriverManager.getConnection("jdbc:oracle:thin:@host:1521:SID","username","userpwd");
         // 將自動(dòng)提交設(shè)置為 false,
         //若設(shè)置為 true 則數(shù)據(jù)庫將會(huì)把每一次數(shù)據(jù)更新認(rèn)定為一個(gè)事務(wù)并自動(dòng)提交
         conn.setAutoCommit(false);
 
         stmt = conn.createStatement(); 
         // 將 A 賬戶中的金額減少 500 
         stmt.execute("\
         update t_account set amount = amount - 500 where account_id = 'A'");
         // 將 B 賬戶中的金額增加 500 
         stmt.execute("\
         update t_account set amount = amount + 500 where account_id = 'B'");
 
         // 提交事務(wù)
         conn.commit();
         // 事務(wù)提交:轉(zhuǎn)賬的兩步操作同時(shí)成功
     } catch(SQLException sqle){            
         try{ 
             // 發(fā)生異常,回滾在本事務(wù)中的操做
            conn.rollback();
             // 事務(wù)回滾:轉(zhuǎn)賬的兩步操作完全撤銷
             stmt.close(); 
             conn.close(); 
         }catch(Exception ignore){ 
 
         } 
         sqle.printStackTrace(); 
     } 
}

javax.sql包的支持
Java.sql.*
包含的接口和類采用傳統(tǒng)的C/S體系結(jié)構(gòu)設(shè)計(jì)思想.主要功能針對(duì)基本數(shù)據(jù)庫編程服務(wù),如生成連接,執(zhí)行語句以及準(zhǔn)備語句和運(yùn)行批處理語句.也有一些高級(jí)功能如批處理更新,可滾動(dòng)結(jié)果集,事務(wù)隔離以及SQL數(shù)據(jù)類型.
javax.sql.*
引入了JDBC編程方面一些主要的體系結(jié)構(gòu)改變,并且為連接管理,分布式事務(wù)處理和老式連接提供了更好的抽象.這個(gè)包也引入了容器管理的連接緩沖池,分布式事務(wù)以及行集(rowset).
java.sql.是jdbc2.0之前的東西
javax.sql.
包括了jdbc3.0的特性

javax.sql.提供了很多新特性,是對(duì)java.sql的補(bǔ)充,具體提供了一下方面的功能
(1)Datasource接口提供了一種可選擇性的方式去建立連接
(2)提供了連接池的支持
(3)增加了分布式的事務(wù)處理機(jī)制
(4)增加了rowset
(注意javax.sql.
并不是包含java.sql.*,它倆一起組成了訪問數(shù)據(jù)的類)

在javax中我們一般要關(guān)注的點(diǎn)是:數(shù)據(jù)源對(duì)象、連接池技術(shù)、分布式事務(wù),在java只定義了接口,沒有實(shí)際的應(yīng)用,所以業(yè)內(nèi)有很多的對(duì)javax.sql的實(shí)現(xiàn)。
DataSrouce接口(數(shù)據(jù)源對(duì)象)、PooledConnection接口(池技術(shù))、XAConnection(分布式事務(wù)),這三個(gè)接口是jdbc的新特性新規(guī)范。
所以一般來說在各個(gè)廠商的實(shí)現(xiàn)來看,pooled一般指連接池,XAxxxx的只分布式。
如果要分析一個(gè)數(shù)據(jù)源的源碼的話,從這三個(gè)方面來看:


image.png

2 druid源碼分析
1 druid是由數(shù)組實(shí)現(xiàn)的
一個(gè)數(shù)據(jù)庫連接池的開源軟件。所以connection連接池的初始化、創(chuàng)建、回收、收縮、獲取,都是關(guān)鍵點(diǎn)。


image.png

通過繼承樹可以看到繼承了好的類和接口,其中主要的有兩個(gè)線,DruidAbstractDataSource和CommonDataSource。說明DruidDataSrouce是一個(gè)DataSource,可以getConnection獲取連接。


image.png

初始化時(shí)在獲取連接的時(shí)候進(jìn)行的,如果沒有手動(dòng)init的話。


image.png

二 鎖、condition、創(chuàng)建\銷毀線程
在druidDataSource中有一個(gè)重入鎖,衍生兩個(gè)condition,一個(gè)監(jiān)控連接池是否為空,一個(gè)監(jiān)控連接池不為空。
在該類中有兩個(gè)線程,一個(gè)生成連接,一個(gè)回收連接。在創(chuàng)建、獲取、回收的時(shí)候都會(huì)使用這些鎖和condition。

image.png

由于數(shù)據(jù)連接數(shù)組是公共資源,所以在多線程并行的情況下,要加鎖使用。
而在用戶線程發(fā)現(xiàn)連接池中沒有資源之后就會(huì)與創(chuàng)建連接的線程進(jìn)行通信。

druid底層是使用數(shù)組實(shí)現(xiàn)的。
獲取連接邏輯:

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
    if (closed) {
        connectErrorCount.incrementAndGet();
        throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
    }
 
    if (!enable) {
        connectErrorCount.incrementAndGet();
        throw new DataSourceDisableException();
    }
 
    final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
    final int maxWaitThreadCount = getMaxWaitThreadCount();
 
    DruidConnectionHolder holder;
    try {
        lock.lockInterruptibly(); // 獲取鎖,所有對(duì)數(shù)組可能的操作都要進(jìn)行加鎖
    } catch (InterruptedException e) {
        connectErrorCount.incrementAndGet();
        throw new SQLException("interrupt", e);
    }
 
    try {
        if (maxWaitThreadCount > 0) {
            if (notEmptyWaitThreadCount >= maxWaitThreadCount) {
                connectErrorCount.incrementAndGet();
                throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                                       + lock.getQueueLength());
            }
        }
 
        connectCount++;
 
        if (maxWait > 0) {
            holder = pollLast(nanos);
        } else {
            holder = takeLast();
        }
 
        if (holder != null) {
            activeCount++;
            if (activeCount > activePeak) {
                activePeak = activeCount;
                activePeakTime = System.currentTimeMillis();
            }
        }
    } catch (InterruptedException e) {
        connectErrorCount.incrementAndGet();
        throw new SQLException(e.getMessage(), e);
    } catch (SQLException e) {
        connectErrorCount.incrementAndGet();
        throw e;
    } finally {
        lock.unlock();
    }
 
    if (holder == null) {
        long waitNanos = waitNanosLocal.get();
 
        StringBuilder buf = new StringBuilder();
        buf.append("wait millis ")//
        .append(waitNanos / (1000 * 1000))//
        .append(", active " + activeCount)//
        ;
 
        List<JdbcSqlStatValue> sqlList = this.getDataSourceStat().getRuningSqlList();
        for (int i = 0; i < sqlList.size(); ++i) {
            if (i != 0) {
                buf.append('\n');
            } else {
                buf.append(", ");
            }
            JdbcSqlStatValue sql = sqlList.get(i);
            buf.append("runningSqlCount ");
            buf.append(sql.getRunningCount());
            buf.append(" : ");
            buf.append(sql.getSql());
        }
 
        String errorMessage = buf.toString();
 
        if (this.createError != null) {
            throw new GetConnectionTimeoutException(errorMessage, createError);
        } else {
            throw new GetConnectionTimeoutException(errorMessage);
        }
    }
 
    holder.incrementUseCount();
 
    DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
    return poolalbeConnection;
}

創(chuàng)建線程邏輯:

lock.lock();
try {
    connections[poolingCount++] = holder;
 
    if (poolingCount > poolingPeak) {
        poolingPeak = poolingCount;
        poolingPeakTime = System.currentTimeMillis();
    }
 
    errorCount = 0; // reset errorCount
 
    notEmpty.signal();
    notEmptySignalCount++;
} finally {
    lock.unlock();
}

回收邏輯:

// 回收方法是在DruidPooledConnection中的close方法中調(diào)用的,DruidPooledConnection是connection的子類,所以調(diào)用方使用框架時(shí),使用close方法就是回收操作。
lock.lockInterruptibly();
try {
    activeCount--;
    closeCount++;
 
    putLast(holder, lastActiveTimeMillis);
    recycleCount++;
} finally {
    lock.unlock();
}

收縮邏輯:

public void shrink(boolean checkTime) {
    final List<DruidConnectionHolder> evictList = new ArrayList<DruidConnectionHolder>();
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        return;
    }
 
    try {
        final int checkCount = poolingCount - minIdle;
        final long currentTimeMillis = System.currentTimeMillis();
        for (int i = 0; i < checkCount; ++i) {
            DruidConnectionHolder connection = connections[i];
 
            if (checkTime) {
                long idleMillis = currentTimeMillis - connection.getLastActiveTimeMillis();  // 根據(jù)timeBetweenEvictionRunsMillis進(jìn)行判斷
                if (idleMillis >= minEvictableIdleTimeMillis) {
                    evictList.add(connection);
                } else {
                    break;
                }
            } else {
                evictList.add(connection);
            }
        }
 
        int removeCount = evictList.size();
        if (removeCount > 0) {// 挪移數(shù)組,刪除引用
            System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);  
            Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
            poolingCount -= removeCount;
        }
    } finally {
        lock.unlock();
    }
 
    for (DruidConnectionHolder item : evictList) { // 關(guān)閉真實(shí)連接
        Connection connection = item.getConnection();
        JdbcUtils.close(connection);
        destroyCount.incrementAndGet();
    }
}

3 數(shù)據(jù)源的配置

4 druid的代理與責(zé)任鏈


image.png

在DruidDataSource中創(chuàng)建的connection是druid自己實(shí)現(xiàn)connection接口的connectionProxyImpl類。是個(gè)代理類,代理的是connection的實(shí)現(xiàn)類,有各個(gè)開發(fā)商實(shí)現(xiàn)的具體的類。

image.png

一個(gè)使用driver生成真實(shí)的連接,一個(gè)對(duì)真實(shí)的連接進(jìn)行封裝成connectionProxy。
而在ds中會(huì)把這個(gè)代理類包裝到holder中,在使用連接池的時(shí)候,獲取holder,從holder中獲取代理連接,在proxy中獲取statment,從而執(zhí)行sql,處理返回?cái)?shù)據(jù)。

image.png

實(shí)際上,druid的對(duì)各個(gè)sql的接口都進(jìn)行了代理,進(jìn)行實(shí)現(xiàn)統(tǒng)計(jì)或者功能。通過源碼可以看到,如果配置了責(zé)任鏈的節(jié)點(diǎn)就會(huì)使用代理類,如果沒有使用則不會(huì)使用代理。

image.png

可以看出,druid的監(jiān)控統(tǒng)計(jì)是使用責(zé)任鏈模式與代理模式實(shí)現(xiàn)的。代理類的作用是調(diào)用過濾責(zé)任鏈。

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,838評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 4,033評(píng)論 0 11
  • JAVA面試題 1、作用域public,private,protected,以及不寫時(shí)的區(qū)別答:區(qū)別如下:作用域 ...
    JA尐白閱讀 1,272評(píng)論 1 0
  • 好久沒在簡書里寫東西了。 今天忽然下起了雨,初夏的雨竟然給了我春雨的感覺,我在心中默默的驚奇。 從圖書館回宿舍的路...
    onematchleft閱讀 550評(píng)論 0 0
  • 一個(gè)小龍女版轉(zhuǎn)世靈童的成長故事:她是黑暗的信徒,直到黑膚黑眼的法師雀鷹把光指給她看。 “青青草坪上兔子哀鳴死去 ,...
    一條污蚣閱讀 467評(píng)論 0 0

友情鏈接更多精彩內(nèi)容