聊聊Druid連接池的內(nèi)部原理及推薦配置
1.前言
一個(gè)正常的SQL執(zhí)行流程為:
- Connection conn = DriverManager.getConnection();
 - Statement stmt = conn.createStatement();
 - ResultSet rs = stmt.executeQuery(sql);
 - 操作rs讀取數(shù)據(jù);
 - 關(guān)閉rs、stmt、conn。
 
示例代碼如下:
public static void main(String[] args){
    try{
        Class.forName("com.mysql.cj.jdbc.Driver"); //調(diào)用Class.forName()方法加載驅(qū)動(dòng)程序
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB?useSSL=false", "root","*****"); //創(chuàng)建連接
        Statement stmt = conn.createStatement(); //創(chuàng)建Statement對(duì)象
 
        String sql = "select * from stu";    //要執(zhí)行的SQL
        ResultSet rs = stmt.executeQuery(sql);//創(chuàng)建數(shù)據(jù)對(duì)象
        System.out.println("編號(hào)"+"\t"+"姓名"+"\t"+"年齡");
        while (rs.next()){
            System.out.print(rs.getInt(1) + "\t");
            System.out.print(rs.getString(2) + "\t");
            System.out.print(rs.getInt(3) + "\t");
            System.out.println();
        }
 
        rs.close();
        stmt.close();
        conn.close();
    }catch(Exception e){
    }
}但如果每次請(qǐng)求都要DriverManager.getConnection()新建連接和關(guān)閉連接,操作較重,費(fèi)時(shí)費(fèi)力,也影響了業(yè)務(wù)請(qǐng)求。 其實(shí)Connection對(duì)象是可以重復(fù)利用的(只要保證Connection借出后歸單一線程所有,其所創(chuàng)建的Statement和ResultSet在回收前都能關(guān)閉即可),這樣Connection被重新獲取后就可以跟新建的一樣,從而避免底層Socket連接的頻繁創(chuàng)建與關(guān)閉。數(shù)據(jù)庫連接池便應(yīng)運(yùn)而生。
DataSource定義了一個(gè)getConnection()的接口,具體實(shí)現(xiàn)可以是直接新建,也可以是從連接池里獲取。用戶使用完Connection后,要手動(dòng)close(),而這個(gè)close()也是個(gè)邏輯語義。對(duì)于MySQL JDBC的ConnectionImpl來說,close()是物理關(guān)閉;而對(duì)于Druid的DruidPooledConnection來說,close()就是歸還。
2.Druid簡(jiǎn)介
當(dāng)我們有了連接池,應(yīng)用程序啟動(dòng)時(shí)就預(yù)先建立多個(gè)數(shù)據(jù)庫連接對(duì)象,然后將連接對(duì)象保存到連接池中。當(dāng)客戶請(qǐng)求到來時(shí),從池中取出一個(gè)連接對(duì)象為客戶服務(wù)。當(dāng)請(qǐng)求完成時(shí),客戶程序調(diào)用關(guān)閉方法,將連接對(duì)象放回池中[2]。跟現(xiàn)實(shí)生活中的共享單車是不是很像~
相比之下,連接池的優(yōu)點(diǎn)顯而易見:
- 資源復(fù)用:因?yàn)閿?shù)據(jù)庫連接可以復(fù)用,避免了頻繁創(chuàng)建和釋放連接引起的大量性能開銷,同時(shí)也增加了系統(tǒng)運(yùn)行環(huán)境的平穩(wěn)性;
 - 提高性能:當(dāng)業(yè)務(wù)請(qǐng)求時(shí),因?yàn)閿?shù)據(jù)庫連接在初始化時(shí)已經(jīng)被創(chuàng)建,可以立即使用,而不需要等待連接的建立,減少了響應(yīng)時(shí)間;
 - 優(yōu)化資源分配:對(duì)于多應(yīng)用共享同一數(shù)據(jù)庫的系統(tǒng)而言,可在應(yīng)用層通過數(shù)據(jù)庫連接池的配置,實(shí)現(xiàn)某一應(yīng)用最大可用數(shù)據(jù)庫連接數(shù)的限制,避免某一應(yīng)用獨(dú)占所有的數(shù)據(jù)庫資源;
 - 連接管理:數(shù)據(jù)庫連接池實(shí)現(xiàn)中,可根據(jù)預(yù)先的占用超時(shí)設(shè)定,強(qiáng)制回收被占用連接,從而避免了常規(guī)數(shù)據(jù)庫連接操作中可能出現(xiàn)的資源泄露。
 
Druid連接池內(nèi)部的數(shù)據(jù)結(jié)構(gòu)如下(以minIdle=5,maxActive=10為例):
- 連接池采用LRU棧式置換策略(最近歸還的會(huì)被最先借出);
 - poolingCount:池中可用的空閑連接;
 - activeCount:已經(jīng)借出去的連接數(shù)。兩者之和為所有連接數(shù)。此時(shí)池里有7個(gè)空閑連接,poolingCount=7;
 - empty條件變量:連接池有空閑連接時(shí)會(huì)等待。獲取連接時(shí)如果無可用空閑連接會(huì)觸發(fā)signal;
 - notEmpty條件變量:獲取連接時(shí)如果為空會(huì)等待,歸還或創(chuàng)建連接時(shí)會(huì)觸發(fā)signal。
 
3.初始化流程init()
觸發(fā)時(shí)機(jī):首次getConnection()時(shí)或直接調(diào)用init()。
核心流程:
- 創(chuàng)建initialSize個(gè)連接;
 - 啟動(dòng)LogStatsThread、CreateConnectionThread、DestroyConnectionThread三個(gè)線程。
 
3.1 LogStatsThread線程(Druid-ConnectionPool-Log-)
如果timeBetweenLogStatsMillis > 0,則每隔timeBetweenLogStatsMillis打印一次stat日志。
3.2 CreateConnectionThread線程(Druid-ConnectionPool-Create-)
圖片
后臺(tái)負(fù)責(zé)創(chuàng)建連接的線程。監(jiān)聽empty條件信號(hào),收到信號(hào)后,如果滿足條件則創(chuàng)建一個(gè)新連接;如果不滿足,則忽略此次信號(hào)。
3.2.1 創(chuàng)建新連接的條件
// 防止創(chuàng)建超過maxActive數(shù)量的連接
if (activeCount + poolingCount >= maxActive) { //如果不滿足條件,則忽略此次信號(hào)并繼續(xù)await()
    empty.await();
    continue; //再次收到empty條件信號(hào)后,重新回到for (;;)處
}3.2.2 createPhysicalConnection()創(chuàng)建連接流程
//創(chuàng)建一條連接,并初始化
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
    //此處省略一萬行
    try {
        Connection conn = createPhysicalConnection(url, physicalConnectProperties); //創(chuàng)建一條物理連接
        connectedNanos = System.nanoTime();
 
        if (conn == null) {
            throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
        }
 
        initPhysicalConnection(conn, variables, globalVariables); //初始化連接
        initedNanos = System.nanoTime();
 
        validateConnection(conn); //檢測(cè)一下
        validatedNanos = System.nanoTime();
 
        setFailContinuous(false);
        setCreateError(null);
    } //此處省略一萬行
 
    return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}接下來再來看createPhysicalConnection(url, info)函數(shù),它就是負(fù)責(zé)創(chuàng)建一條java.sql.Connection連接,如下:
public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
    Connection conn;
    if (getProxyFilters().size() == 0) {
        conn = getDriver().connect(url, info); //創(chuàng)建一條連接
    } else {
        conn = new FilterChainImpl(this).connection_connect(info);
    }
 
    return conn;
}3.2.3 put(holder, createTaskId)將連接放入連接池
將連接放入連接池尾部,并發(fā)送notEmpty條件信號(hào)。
//將連接放入連接池尾部,并發(fā)送notEmpty條件信號(hào)
private boolean put(DruidConnectionHolder holder, long createTaskId) {
    lock.lock();
    try {
        if (poolingCount >= maxActive) {
            return false;
        }
         
        connections[poolingCount] = holder; //放入連接池尾部
        incrementPoolingCount(); //可用連接數(shù)poolingCount+1
 
        notEmpty.signal(); //發(fā)送notEmpty條件信號(hào)
        notEmptySignalCount++;
    } finally {
        lock.unlock();
    }
    return true;
}3.3 DestroyConnectionThread線程(Druid-ConnectionPool-Destroy-)
圖片
定時(shí)掃描連接池進(jìn)行探測(cè)和銷毀。DestroyConnectionThread每隔timeBetweenEvictionRunsMillis掃描一次連接池中的空閑連接:
- 如果物理存活時(shí)間超過phyTimeoutMillis,則直接銷毀;
 - 如果( keepAlive && 空閑時(shí)間 >= keepAliveBetweenTimeMillis),則進(jìn)行探測(cè);
 - 如果空閑時(shí)間 >= minEvictableIdleTimeMillis,則銷毀(但要保證留下minIdle個(gè));而如果空閑時(shí)間超過maxEvictableIdleTimeMillis則必須進(jìn)行銷毀;
 - 如果( removeAbandoned == true && 連接借出時(shí)間 > removeAbandonedTimeoutMillis),則強(qiáng)制關(guān)閉其statement并歸還。
 
public void run() {
    shrink(true, keepAlive); //探測(cè)與銷毀
 
    if (isRemoveAbandoned()) { //檢查連接泄漏
        removeAbandoned();
    }
}3.3.1 shrink(checkTime, keepAlive)流程
public void shrink(boolean checkTime, boolean keepAlive) {
    try {
        final int checkCount = poolingCount - minIdle;
        final long currentTimeMillis = System.currentTimeMillis();
        for (int i = 0; i < poolingCount; ++i) {
            DruidConnectionHolder connection = connections[i];
 
            if (phyTimeoutMillis > 0) { //如果連接的物理存活時(shí)間超過限值,將被銷毀。
                long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
                if (phyConnectTimeMillis > phyTimeoutMillis) {
                    evictConnections[evictCount++] = connection;
                    continue;
                }
            }
 
            long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis; //空閑時(shí)間
 
             //如果空閑時(shí)間過短,直接跳過
            if (idleMillis < minEvictableIdleTimeMillis
                    && idleMillis < keepAliveBetweenTimeMillis
            ) {
                break;
            }
 
            if (idleMillis >= minEvictableIdleTimeMillis) { //如果連接空閑時(shí)間超過minEvictableIdleTimeMillis
                if (checkTime && i < checkCount) { //留尾部的minIdle個(gè)連接先不銷毀
                    evictConnections[evictCount++] = connection;
                    continue;
                } else if (idleMillis > maxEvictableIdleTimeMillis) { //尾部的minIdle個(gè)連接如果連接空閑時(shí)間>maxEvictableIdleTimeMillis,也會(huì)被銷毀
                    evictConnections[evictCount++] = connection;
                    continue;
                }
            }
 
            if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) { //如果連接空閑時(shí)間未超過minEvictableIdleTimeMillis,但超過了keepAliveBetweenTimeMillis就要進(jìn)行探活
                keepAliveConnections[keepAliveCount++] = connection;
            }
        }
 
    } finally {
        lock.unlock();
    }
 
    if (evictCount > 0) { //如果有需要銷毀的,則進(jìn)行關(guān)閉連接操作。
        for (int i = 0; i < evictCount; ++i) {
            DruidConnectionHolder item = evictConnections[i];
            Connection connection = item.getConnection();
            JdbcUtils.close(connection);
            destroyCountUpdater.incrementAndGet(this);
        }
        Arrays.fill(evictConnections, null);
    }
 
    if (keepAliveCount > 0) { //如果有需要探測(cè)的,則進(jìn)行探測(cè)。探活的,則放回可用池;探不活的,直接關(guān)閉,并通知?jiǎng)?chuàng)建。
        // keep order
        for (int i = keepAliveCount - 1; i >= 0; --i) {
            //此處省略一萬行
        }
    }
 
    if (needFill) {
        lock.lock();
        try {
            int fillCount = minIdle - (activeCount + poolingCount + createTaskCount); //需要補(bǔ)充創(chuàng)建的連接個(gè)數(shù)
            for (int i = 0; i < fillCount; ++i) {
                emptySignal(); //給CreateConnectionThread發(fā)送empty條件信號(hào)來創(chuàng)建連接
            }
        } finally {
            lock.unlock();
        }
    }
}3.3.2 removeAbandoned()連接泄漏檢測(cè)
連接泄露檢查,打開removeAbandoned功能,連接從連接池借出后,長(zhǎng)時(shí)間不歸還,將觸發(fā)強(qiáng)制關(guān)閉其staement并歸還?;厥罩芷陔StimeBetweenEvictionRunsMillis進(jìn)行,如果連接借出時(shí)間起超過removeAbandonedTimeout,則強(qiáng)制關(guān)閉其staement并歸還。對(duì)性能會(huì)有一些影響,建議懷疑存在泄漏之后再打開,不建議在生產(chǎn)環(huán)境中使用。
//強(qiáng)制歸還泄漏(借出后長(zhǎng)時(shí)間未歸還)的連接
public int removeAbandoned() {
    long currrentNanos = System.nanoTime();
    List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
 
    activeConnectionLock.lock();
    try {
        Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
        for (; iter.hasNext();) {
            DruidPooledConnection pooledConnection = iter.next();
 
            if (pooledConnection.isRunning()) {
                continue;
            }
 
            long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000); //連接借出時(shí)間
 
            if (timeMillis >= removeAbandonedTimeoutMillis) {
                iter.remove();
                abandonedList.add(pooledConnection);
            }
        }
    } finally {
        activeConnectionLock.unlock();
    }
 
    if (abandonedList.size() > 0) {
        for (DruidPooledConnection pooledConnection : abandonedList) {
            JdbcUtils.close(pooledConnection); //強(qiáng)制歸還
        }
    }
 
    return removeCount;
}4.獲取連接流程getConnection()
圖片
連接池最核心的功能就是連接的獲取與回收。我們直接看getConnectionDirect(),它負(fù)責(zé)獲取一個(gè)可用的連接。
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
    for (;;) { //如果某次獲取到的連接無效,一般會(huì)丟棄該連接并重新獲取。
        DruidPooledConnection poolableConnection;
        try {
            poolableConnection = getConnectionInternal(maxWaitMillis); //獲取連接
        } catch (GetConnectionTimeoutException ex) {
            //......
        }
 
        if (testOnBorrow) { //如果testOnBorrow=true,則進(jìn)行探測(cè)。
            boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
            if (!validate) {
                discardConnection(poolableConnection.holder); //探測(cè)失敗,則丟棄此連接并重新獲取。
                continue;
            }
        } else {
            if (testWhileIdle) { //如果testWhileIdle=true且空閑時(shí)間>timeBetweenEvictionRunsMillis,則進(jìn)行探測(cè)。
                final DruidConnectionHolder holder = poolableConnection.holder;
                long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;
 
                long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
 
                if (idleMillis >= timeBetweenEvictionRunsMillis
                        || idleMillis < 0 // unexcepted branch
                        ) {
                    boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                    if (!validate) {
                        discardConnection(poolableConnection.holder); //如果探測(cè)失敗,則丟棄連接并重新獲取。
                         continue;
                    }
                }
            }
        }
 
        //是否打開連接泄露檢查。DestroyConnectionThread如果檢測(cè)到連接借出時(shí)間超過removeAbandonedTimeout,則強(qiáng)制歸還連接到連接池中。
        //對(duì)性能會(huì)有一些影響,建議懷疑存在泄漏之后再打開,不建議在生產(chǎn)環(huán)境中使用。
        if (removeAbandoned) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); //保存發(fā)起方的線程棧
            poolableConnection.connectStackTrace = stackTrace;
            poolableConnection.setConnectedTimeNano(); //重置借出時(shí)間
 
            activeConnectionLock.lock();
            try {
                activeConnections.put(poolableConnection, PRESENT); //放進(jìn)activeConnections
            } finally {
                activeConnectionLock.unlock();
            }
        }
 
        return poolableConnection;
    }
}4.1 getConnectionInternal()獲取一個(gè)連接
getConnectionInternal()負(fù)責(zé)從連接池獲取一個(gè)連接(但該連接并不保證可用),并包裝成DruidPooledConnection。
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
    final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
    final int maxWaitThreadCount = this.maxWaitThreadCount;
    DruidConnectionHolder holder;
 
    for (boolean createDirect = false;;) {
        try {
            //......
 
            if (maxWaitThreadCount > 0
                    && notEmptyWaitThreadCount >= maxWaitThreadCount) { //如果等待獲取連接的線程數(shù)超過maxWaitThreadCount,則拋出異常
                throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                        + lock.getQueueLength());
            }
 
             //從可用連接池里獲取連接,如果沒有則阻塞等待。
            if (maxWait > 0) {
                holder = pollLast(nanos);
            } else {
                holder = takeLast();
            }
 
            //......
        } //......
 
        break;
    }
 
    //......
    holder.incrementUseCount(); //連接的使用次數(shù)+1
 
    DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); ////包裝成一個(gè)DruidPooledConnection對(duì)象
    return poolalbeConnection;
}4.2 takeLast()阻塞等待尾部連接
//阻塞等待尾部連接
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
    try {
        while (poolingCount == 0) {  //如果沒有可用連接,就發(fā)送個(gè)empty條件信號(hào)給CreateConnectionThread,并等待notEmpty條件信號(hào)
            emptySignal(); // send signal to CreateThread create connection
 
            notEmptyWaitThreadCount++;
            try {
                notEmpty.await(); // signal by recycle or creator
            } finally {
                notEmptyWaitThreadCount--;
            }
 
            //......
        }
    } catch (InterruptedException ie) {
        //......
    }
 
     //移除尾部連接
    decrementPoolingCount();
    DruidConnectionHolder last = connections[poolingCount];
    connections[poolingCount] = null;
 
    return last;
}至此,向請(qǐng)求線程返回一個(gè)可用的連接DruidPooledConnection。
5.執(zhí)行&異常處理
圖片
如下為Mybatis執(zhí)行SQL的核心函數(shù)(SqlSessionTemplate$SqlSessionInterceptor.invoke()):
private class SqlSessionInterceptor implements InvocationHandler {
 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //......
        try {
            Object result = method.invoke(sqlSession, args); //下調(diào)DruidPooledPreparedStatement.execute()執(zhí)行SQL
            unwrapped = result;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            //......
            throw (Throwable)unwrapped; //如果發(fā)生異常,繼續(xù)上拋
        } finally {
            if (sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); //關(guān)閉連接,對(duì)應(yīng)Druid是歸還。
            }
 
        }
 
        return unwrapped;
    }
}- method.invoke()會(huì)通過Druid獲取連接,并調(diào)用DruidPooledPreparedStatement.execute();
 - 執(zhí)行結(jié)束后,close連接,此時(shí)會(huì)觸發(fā)Druid的連接歸還;
 - 執(zhí)行中如果發(fā)生異常,繼續(xù)向上拋。
 
5.1 DruidPooledPreparedStatement.execute()
此時(shí),進(jìn)入Druid連接中statement的execute(),如果發(fā)生異常進(jìn)入checkException()。
public boolean execute() throws SQLException {
    conn.beforeExecute();
    try {
        return stmt.execute(); //執(zhí)行SQL
    } catch (Throwable t) {
        errorCheck(t);
 
        throw checkException(t); //如果發(fā)生異常,調(diào)用DruidDataSource.handleConnectionException()對(duì)連接進(jìn)行處理,并繼續(xù)上拋
    } finally {
        conn.afterExecute();
    }
}5.1.1 DruidDataSource.handleConnectionException()
public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t, String sql) throws SQLException {
    //......
    if (t instanceof SQLException) {
        SQLException sqlEx = (SQLException) t;
 
        // exceptionSorter.isExceptionFatal
        if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) { //判斷是否是致命異常
            handleFatalError(pooledConnection, sqlEx, sql); //如果是致命異常,則銷毀
        }
 
        throw sqlEx;
    } else {
        throw new SQLException("Error", t);
    }
}//對(duì)致命異常進(jìn)行處理
protected final void handleFatalError(DruidPooledConnection conn, SQLException error, String sql ) throws SQLException {
    final DruidConnectionHolder holder = conn.holder;
    //......
 
    if (requireDiscard) {
        if (holder != null && holder.statementTrace != null) {
            try {
                for (Statement stmt : holder.statementTrace) { //關(guān)閉連接內(nèi)的所有的statement
                    JdbcUtils.close(stmt);
                }
            } finally {
            }
        }
 
        emptySignalCalled = this.discardConnection(holder); //銷毀
    }
    LOG.error("{conn-" + (holder != null ? holder.getConnectionId() : "null") + "} discard", error);
 
    //......
}6.回收連接流程recycle()
圖片
使用DruidPooledConnection連接進(jìn)行SQL操作后,會(huì)調(diào)用DruidPooledConnection.recycle()進(jìn)行回收操作。
public void recycle() throws SQLException {
    DruidConnectionHolder holder = this.holder;
    DruidAbstractDataSource dataSource = holder.getDataSource();
    dataSource.recycle(this);
 
    //......
}6.1 DruidDataSource.recycle()
/**
 * 回收連接
 * 1)重置連接;2)歸還到連接池。
 */
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
    final DruidConnectionHolder holder = pooledConnection.holder;
    final Connection physicalConnection = holder.conn;
 
    try {
         //歸還前重置連接
         holder.reset();
 
        if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) { //限制連接的最大使用次數(shù)。超過此值,會(huì)被直接關(guān)閉。
            discardConnection(holder);
            return;
        }
 
        if (testOnReturn) { //如果testOnReturn=true,歸還前也檢測(cè)下
            //......
        }
 
        final long currentTimeMillis = System.currentTimeMillis();
        if (phyTimeoutMillis > 0) { //檢測(cè)物理存活時(shí)間
            long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
            if (phyConnectTimeMillis > phyTimeoutMillis) {
                discardConnection(holder);
                return;
            }
        }
 
        lock.lock();
        try {
            result = putLast(holder, currentTimeMillis); //歸還到連接池
            recycleCount++;
        } finally {
            lock.unlock();
        }
 
        if (!result) {
            JdbcUtils.close(holder.conn);
        }
    } catch (Throwable e) {
        //......
    }
}6.2 將連接放入可用連接池尾部,并發(fā)送notEmpty條件信號(hào)
//將連接放入可用連接池尾部,并發(fā)送notEmpty條件信號(hào)
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
    if (poolingCount >= maxActive || e.discard) {
        return false;
    }
 
    e.lastActiveTimeMillis = lastActiveTimeMillis; //重置最近活躍時(shí)間
    connections[poolingCount] = e; //歸還到尾部
    incrementPoolingCount();
 
    notEmpty.signal();
    notEmptySignalCount++;
 
    return true;
}至此,連接已成功回收。
7.總結(jié)
7.1 整個(gè)連接池的核心操作
- init()初始化:1)創(chuàng)建initialSize個(gè)連接;2)啟動(dòng)LogStatsThread、CreateConnectionThread、DestroyConnectionThread三個(gè)線程;
 - getConnection()獲取連接:獲取后會(huì)從連接池移除,Connection只能歸當(dāng)前線程所用;
 - recycle()回收連接:放回連接池后,其他線程就可以再次獲取該連接重復(fù)利用了。
 
7.2 條件信號(hào)協(xié)作
- 獲取連接時(shí):如果連接池里沒有連接,會(huì)發(fā)出empty條件信號(hào),并等待notEmpty條件信號(hào)。CreateConnectionThread收到empty信號(hào)后,如果滿足條件則創(chuàng)建一個(gè)新連接,也會(huì)發(fā)出notEmpty條件信號(hào);如果不滿足,則忽略此次empty信號(hào)。
 - 回收連接時(shí):連接放回連接池后,會(huì)發(fā)出notEmpty條件信號(hào)。如果有請(qǐng)求在阻塞等待獲取連接,此時(shí)會(huì)被喚醒,從而獲取連接。
 
7.3 幾處檢測(cè)和銷毀邏輯
- 借出時(shí):
 
如果testOnBorrow,則探測(cè);
如果(testWhileIdle = true && 空閑時(shí)間 > timeBetweenEvictionRunsMillis)則進(jìn)行探測(cè);
- 執(zhí)行時(shí):
 - 如果拋出異常且exceptionSorter判斷是致命異常,就調(diào)用handleFatalError()進(jìn)行銷毀;
 - 歸還時(shí):
 - 如果連接使用次數(shù)超過phyMaxUseCount,則銷毀;
 - 如果testOnReturn=true,則探測(cè);
 - 如果連接建立時(shí)間走過phyTimeoutMillis,則銷毀;
 - DestroyConnectionThread每隔timeBetweenEvictionRunsMillis掃描一次連接池中的空閑連接:
 - 如果物理存活時(shí)間超過phyTimeoutMillis,則銷毀;
 - 如果( keepAlive && 空閑時(shí)間 >= keepAliveBetweenTimeMillis),則進(jìn)行探測(cè);
 - 如果空閑時(shí)間 >= minEvictableIdleTimeMillis,則銷毀(但要保證留下minIdle個(gè));而如果空閑時(shí)間超過maxEvictableIdleTimeMillis則必須銷毀;
 - 如果( removeAbandoned == true && 連接借出時(shí)間 > removeAbandonedTimeoutMillis),則被強(qiáng)制關(guān)閉其statement并歸還。
 
8.常用&推薦配置
8.1 常用配置
官方完整介紹見:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
圖片
圖片
圖片
8.2 推薦配置
<bean id="userdataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- 基本屬性 -->
    <property name="name" value="userdataSource" />
    <property name="url" value="${userdataSource_url}" />
    <property name="driverClassName" value="com.zhuanzhuan.arch.kms.jdbc.mysql.Driver" />
       
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="1" />
    <property name="minIdle" value="3" />
    <property name="maxActive" value="20" />
  
    <!-- 獲取連接的等待超時(shí)時(shí)間,單位是毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會(huì)有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 -->
    <property name="maxWait" value="60000" />
 
    <!-- 探測(cè)的SQL -->
    <property name="validationQuery" value="SELECT 1" />
    <!-- 獲取連接時(shí),執(zhí)行validationQuery檢測(cè)連接是否有效 -->
    <property name="testOnBorrow" value="false" />
    <!-- 獲取連接時(shí),如果空閑時(shí)間超過timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測(cè)連接是否有效 -->
    <property name="testWhileIdle" value="true" />
    <!-- 歸還連接時(shí),執(zhí)行validationQuery檢測(cè)連接是否有效 -->
    <property name="testOnReturn" value="false" />
  
    <!-- 定期檢測(cè)的時(shí)間間隔,單位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000" />
    <!-- 定期檢測(cè)時(shí),如果空閑時(shí)間超過此值則進(jìn)行銷毀(但要保證留下minIdle個(gè)連接),單位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000" />
</bean>此配置說明:
- 線程池剛啟動(dòng)時(shí)會(huì)創(chuàng)建1個(gè)(initialSize)連接,隨著程序的運(yùn)行,池不忙時(shí)也會(huì)保持最少3個(gè)(minIdle)空閑連接,但總連接數(shù)(包括空閑和在用)不超過20個(gè)(maxActive);
 - 獲取連接時(shí):
 
如果連接池沒有空閑連接,最長(zhǎng)等待60秒(maxWait);
【主動(dòng)】如果獲取到的連接空閑時(shí)間大于60秒(timeBetweenEvictionRunsMillis),則執(zhí)行validationQuery檢測(cè)連接是否還有效(有效則使用,無效則銷毀);
- 執(zhí)行SQL時(shí):
 - 【被動(dòng)】如果發(fā)生致命異常(默認(rèn)exceptionSorter=MySqlExceptionSorter,如CommunicationsException),則銷毀該連接;
 - DestroyConnectionThread每隔60秒(timeBetweenEvictionRunsMillis)掃描一次連接池中的空閑連接:
 - 【主動(dòng)】如果空閑時(shí)間超過300秒(minEvictableIdleTimeMillis),則銷毀(但要保證留下minIdle=3個(gè));而如果空閑時(shí)間超過7小時(shí)(maxEvictableIdleTimeMillis默認(rèn)為7小時(shí))則必須銷毀。
 
9.監(jiān)控
Druid通過SPI開放了很多擴(kuò)展點(diǎn),我們架構(gòu)部基于此封裝了監(jiān)控組件,直接上報(bào)到Prometheus。效果如下:
圖片
圖片
關(guān)于作者
杜云杰,高級(jí)架構(gòu)師,轉(zhuǎn)轉(zhuǎn)架構(gòu)部負(fù)責(zé)人,轉(zhuǎn)轉(zhuǎn)技術(shù)委員會(huì)執(zhí)行主席,騰訊云MVP。負(fù)責(zé)服務(wù)治理、MQ、云平臺(tái)、APM、IM、分布式調(diào)用鏈路追蹤、監(jiān)控系統(tǒng)、配置中心、分布式任務(wù)調(diào)度平臺(tái)、分布式ID生成器、分布式鎖等基礎(chǔ)組件。微信號(hào):waterystone,歡迎建設(shè)性交流。















 
 
 






 
 
 
 