druid 源碼分析

前言

druid是阿里爸爸的開源數據庫連接池,據說其性能算是位於領先的水平,從連接的創建和銷燬這個性能方面優於其它連接池,但是覺得和HikariCP,的速度比起來還是差點。但是兩者各有好處,一個是擴展性比較優秀功能比較全,一個是速度比較塊。以下是性能對照圖:

圖片出處:https://github.com/brettwooldridge/HikariCP 
這裏寫圖片描述
圖片出處:druid Github 
這裏寫圖片描述

圖片出處:http://blog.didispace.com/java-datasource-pool-compare/ 
這裏寫圖片描述 
這裏寫圖片描述 
於是便引起了我這個菜鳥的好奇心,由於druid功能強大,我是菜鳥,就看看連接池的一些內容吧。

小試牛刀-使用

配置:

配置列表,其兼容DBCP的各項配置,但是有些許的語義區別。以下是我簡單的入門配置。

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14_customer
username=root
password=

#初始鏈接數,在連接池被創建的時候初始化的連接數
initSize=20
#最大連接池數量
maxActive=20
#最小連接池數量
minIdle=5

#超時等待時間
maxWait=60000

#指定連接屬性
connectionProperties=useSSL=true;rewriteBatchedStatements=true

在java中簡單使用:

package Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class Druid {
    private static DataSource dataSource;

    static {
        try {
            InputStream inputStream = DBCP.class.getClassLoader().getResourceAsStream("dbconfig.properties");
            Properties properties = new Properties();
            properties.load(inputStream);
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public static void release(Connection conn, Statement st, ResultSet rs) {
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

挺簡單的沒啥好說的

看看源碼

重要變量

DruidAbstractDataSource.class

//重入鎖
protected ReentrantLock lock;
//empty,notEmpty條件,負責線程的阻塞喚醒
protected Condition notEmpty;
protected Condition empty;

DruidSource.class

//存放線程的底層數組
private volatile DruidConnectionHolder[] connections;

工廠函數創建datasource

public static DataSource createDataSource(Map properties) throws Exception {
    DruidDataSource dataSource = new DruidDataSource();
    //從properties中配置datasource
    config(dataSource, properties);
    return dataSource;
}

在獲取連接的過程進行初始化

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
    this.init();
    if (this.filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(this);
        return filterChain.dataSource_connect(this, maxWaitMillis);
    } else {
        return this.getConnectionDirect(maxWaitMillis);
    }
}

初始化的方法中有這樣的幾個線程啓動,一個是create一個是destroy

this.createAndLogThread();
 this.createAndStartCreatorThread();
 this.createAndStartDestroyThread();

lock、condition、兩個線程-創建/銷燬connection
從源碼我們可以看出druid連接池是基於ReetrantLock的,它有兩個condition,empty和notEmpty,一個是監控連接池是否爲空一個是監控連接池不爲空的。這是連接池的核心部分,讓我們一探究竟。注意,這兩個條件condition都是誰用?等待爲空的線程是創建condition的線程,等待不爲空的是getConnection。

創建線程:
com.alibaba.druid.pool.DruidDataSource.CreateConnectionThread
 

try {
    //等待爲空的變量,依據這個變量來進行condition:empty的阻塞(awiait)
    boolean emptyWait = true;
    if (DruidDataSource.this.createError != null && DruidDataSource.this.poolingCount == 0 && !discardChanged) {
    emptyWait = false;
    }

    if (emptyWait && DruidDataSource.this.asyncInit && DruidDataSource.this.createCount < (long)DruidDataSource.this.initialSize) {
    emptyWait = false;
    }

    if (emptyWait) {
    if (DruidDataSource.this.poolingCount >= DruidDataSource.this.notEmptyWaitThreadCount && (!DruidDataSource.this.keepAlive || DruidDataSource.this.activeCount + DruidDataSource.this.poolingCount >= DruidDataSource.this.minIdle)) {
       DruidDataSource.this.empty.await();
    }

    if (DruidDataSource.this.activeCount + DruidDataSource.this.poolingCount >= DruidDataSource.this.maxActive) {
       DruidDataSource.this.empty.await();
       continue;
    }
}

等待空的兩個if條件

  • 池中的連接數量>=等待不爲空的線程數量.意思就是說你請求一個連接我連接池裏面有,這個時候就不用創建連接了,連接的線程就await,阻塞
  • 活躍的數量+池中的數量>=連接池總數量,意思就是說連接池數量達到了池的上限,這個時候就不能再創建連接了。
if (connection != null) {
    // 往線程中put進一個連接connection
    boolean result = DruidDataSource.this.put(connection);
    if (!result) {
        JdbcUtils.close(connection.getPhysicalConnection());
        DruidDataSource.LOG.info("put physical connection to pool failed.");
    }

    errorCount = 0;
}

獲取線程

獲取線程的邏輯主要是它來實現的,讓我們進一步探究一下。

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
    this.init();
    if (this.filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(this);
        return filterChain.dataSource_connect(this, maxWaitMillis);
    } else {
        return this.getConnectionDirect(maxWaitMillis);
    }
}

最後的彈出連接是這個方法:

DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
    try {
        while(this.poolingCount == 0) {
            //連接池空了,喚醒生產者來創建連接
            this.emptySignal();
            if (this.failFast && this.failContinuous.get()) {
                throw new DataSourceNotAvailableException(this.createError);
            }

            ++this.notEmptyWaitThreadCount;
            if (this.notEmptyWaitThreadCount > this.notEmptyWaitThreadPeak) {
                this.notEmptyWaitThreadPeak = this.notEmptyWaitThreadCount;
            }

            //阻塞了一個,數量也就-1
            try {
                this.notEmpty.await();
            } finally {
                --this.notEmptyWaitThreadCount;
            }

            ++this.notEmptyWaitCount;
            if (!this.enable) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new DataSourceDisableException();
            }
        }
    } catch (InterruptedException var5) {
        this.notEmpty.signal();
        ++this.notEmptySignalCount;
        throw var5;
    }
    //減少池中的連接計數,下面的部分就是取連接的操作
    this.decrementPoolingCount();
    DruidConnectionHolder last = this.connections[this.poolingCount];
    this.connections[this.poolingCount] = null;
    return last;
}

兩個有用的字段:notEmptyWaitThreadCount這個字段也就是消費者數量,notEmptyWaitCount維護的就是正在等待的消費者數量 
這讓我就突然想起了生產者和消費者的思想,創建線程是生產者,獲取線程是消費者,不難發現notEmpty這個Condition是用在了它上面。

銷燬線程
 

public class DestroyTask implements Runnable {
    public DestroyTask() {
    }

    public void run() {
        DruidDataSource.this.shrink(true, DruidDataSource.this.keepAlive);
        if (DruidDataSource.this.isRemoveAbandoned()) {
            DruidDataSource.this.removeAbandoned();
        }

    }
}

shrink的關鍵就在這,其主要功能是收縮就是空閒的太多了要進行回收了。

if (evictCount > 0) {
    for(checkCount = 0; checkCount < evictCount; ++checkCount) {
        holer = this.evictConnections[checkCount];
        connection = holer.getConnection();
        JdbcUtils.close(connection);
        destroyCountUpdater.incrementAndGet(this);
    }

    Arrays.fill(this.evictConnections, (Object)null);
}

總結

這次的小blog只是就簡單的實現原理看了看,可以看出連接池是生產者消費者這種形式運作的,主要是兩個線程來保證連接池,其中很多細節未涉及,筆者能力有限。如果有機會還想學習一下監控,據說是通過責任鏈來實現的,責任鏈我不懂,就沒有深入。總的來說,旨在入個門把。

本來想自己寫一個源碼分析的,馬上下班了,先轉一個,有時間在自己寫

轉:https://blog.csdn.net/qq_41376740/article/details/81869261

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章