成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊聊Druid連接池的內部原理及推薦配置

開發 前端
DataSource定義了一個getConnection()的接口,具體實現可以是直接新建,也可以是從連接池里獲取。用戶使用完Connection后,要手動close(),而這個close()也是個邏輯語義。對于MySQL JDBC的ConnectionImpl來說,close()是物理關閉;而對于Druid的DruidPooledConnection來說,close()就是歸還。

1.前言

一個正常的SQL執行流程為:

  • Connection conn = DriverManager.getConnection();
  • Statement stmt = conn.createStatement();
  • ResultSet rs = stmt.executeQuery(sql);
  • 操作rs讀取數據;
  • 關閉rs、stmt、conn。

示例代碼如下:

public static void main(String[] args){
    try{
        Class.forName("com.mysql.cj.jdbc.Driver"); //調用Class.forName()方法加載驅動程序
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB?useSSL=false", "root","*****"); //創建連接
        Statement stmt = conn.createStatement(); //創建Statement對象
 
        String sql = "select * from stu";    //要執行的SQL
        ResultSet rs = stmt.executeQuery(sql);//創建數據對象
        System.out.println("編號"+"\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){
    }
}

但如果每次請求都要DriverManager.getConnection()新建連接和關閉連接,操作較重,費時費力,也影響了業務請求。 其實Connection對象是可以重復利用的(只要保證Connection借出后歸單一線程所有,其所創建的Statement和ResultSet在回收前都能關閉即可),這樣Connection被重新獲取后就可以跟新建的一樣,從而避免底層Socket連接的頻繁創建與關閉。數據庫連接池便應運而生。

DataSource定義了一個getConnection()的接口,具體實現可以是直接新建,也可以是從連接池里獲取。用戶使用完Connection后,要手動close(),而這個close()也是個邏輯語義。對于MySQL JDBC的ConnectionImpl來說,close()是物理關閉;而對于Druid的DruidPooledConnection來說,close()就是歸還。

2.Druid簡介

當我們有了連接池,應用程序啟動時就預先建立多個數據庫連接對象,然后將連接對象保存到連接池中。當客戶請求到來時,從池中取出一個連接對象為客戶服務。當請求完成時,客戶程序調用關閉方法,將連接對象放回池中[2]。跟現實生活中的共享單車是不是很像~圖片

相比之下,連接池的優點顯而易見:

  • 資源復用:因為數據庫連接可以復用,避免了頻繁創建和釋放連接引起的大量性能開銷,同時也增加了系統運行環境的平穩性;
  • 提高性能:當業務請求時,因為數據庫連接在初始化時已經被創建,可以立即使用,而不需要等待連接的建立,減少了響應時間;
  • 優化資源分配:對于多應用共享同一數據庫的系統而言,可在應用層通過數據庫連接池的配置,實現某一應用最大可用數據庫連接數的限制,避免某一應用獨占所有的數據庫資源;
  • 連接管理:數據庫連接池實現中,可根據預先的占用超時設定,強制回收被占用連接,從而避免了常規數據庫連接操作中可能出現的資源泄露。

Druid連接池內部的數據結構如下(以minIdle=5,maxActive=10為例):圖片

  • 連接池采用LRU棧式置換策略(最近歸還的會被最先借出);
  • poolingCount:池中可用的空閑連接;
  • activeCount:已經借出去的連接數。兩者之和為所有連接數。此時池里有7個空閑連接,poolingCount=7;
  • empty條件變量:連接池有空閑連接時會等待。獲取連接時如果無可用空閑連接會觸發signal;
  • notEmpty條件變量:獲取連接時如果為空會等待,歸還或創建連接時會觸發signal。

3.初始化流程init()

圖片觸發時機:首次getConnection()時或直接調用init()。

核心流程:

  • 創建initialSize個連接;
  • 啟動LogStatsThread、CreateConnectionThread、DestroyConnectionThread三個線程。

3.1 LogStatsThread線程(Druid-ConnectionPool-Log-)

如果timeBetweenLogStatsMillis > 0,則每隔timeBetweenLogStatsMillis打印一次stat日志。

3.2 CreateConnectionThread線程(Druid-ConnectionPool-Create-)

圖片圖片

后臺負責創建連接的線程。監聽empty條件信號,收到信號后,如果滿足條件則創建一個新連接;如果不滿足,則忽略此次信號。

3.2.1 創建新連接的條件

// 防止創建超過maxActive數量的連接
if (activeCount + poolingCount >= maxActive) { //如果不滿足條件,則忽略此次信號并繼續await()
    empty.await();
    continue; //再次收到empty條件信號后,重新回到for (;;)處
}
3.2.2 createPhysicalConnection()創建連接流程
//創建一條連接,并初始化
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
    //此處省略一萬行
    try {
        Connection conn = createPhysicalConnection(url, physicalConnectProperties); //創建一條物理連接
        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); //檢測一下
        validatedNanos = System.nanoTime();
 
        setFailContinuous(false);
        setCreateError(null);
    } //此處省略一萬行
 
    return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}

接下來再來看createPhysicalConnection(url, info)函數,它就是負責創建一條java.sql.Connection連接,如下:

public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
    Connection conn;
    if (getProxyFilters().size() == 0) {
        conn = getDriver().connect(url, info); //創建一條連接
    } else {
        conn = new FilterChainImpl(this).connection_connect(info);
    }
 
    return conn;
}

3.2.3 put(holder, createTaskId)將連接放入連接池

將連接放入連接池尾部,并發送notEmpty條件信號。

//將連接放入連接池尾部,并發送notEmpty條件信號
private boolean put(DruidConnectionHolder holder, long createTaskId) {
    lock.lock();
    try {
        if (poolingCount >= maxActive) {
            return false;
        }
         
        connections[poolingCount] = holder; //放入連接池尾部
        incrementPoolingCount(); //可用連接數poolingCount+1
 
        notEmpty.signal(); //發送notEmpty條件信號
        notEmptySignalCount++;
    } finally {
        lock.unlock();
    }
    return true;
}

3.3 DestroyConnectionThread線程(Druid-ConnectionPool-Destroy-)

圖片圖片

定時掃描連接池進行探測和銷毀。DestroyConnectionThread每隔timeBetweenEvictionRunsMillis掃描一次連接池中的空閑連接:

  • 如果物理存活時間超過phyTimeoutMillis,則直接銷毀;
  • 如果( keepAlive && 空閑時間 >= keepAliveBetweenTimeMillis),則進行探測;
  • 如果空閑時間 >= minEvictableIdleTimeMillis,則銷毀(但要保證留下minIdle個);而如果空閑時間超過maxEvictableIdleTimeMillis則必須進行銷毀;
  • 如果( removeAbandoned == true && 連接借出時間 > removeAbandonedTimeoutMillis),則強制關閉其statement并歸還。
public void run() {
    shrink(true, keepAlive); //探測與銷毀
 
    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) { //如果連接的物理存活時間超過限值,將被銷毀。
                long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
                if (phyConnectTimeMillis > phyTimeoutMillis) {
                    evictConnections[evictCount++] = connection;
                    continue;
                }
            }
 
            long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis; //空閑時間
 
             //如果空閑時間過短,直接跳過
            if (idleMillis < minEvictableIdleTimeMillis
                    && idleMillis < keepAliveBetweenTimeMillis
            ) {
                break;
            }
 
            if (idleMillis >= minEvictableIdleTimeMillis) { //如果連接空閑時間超過minEvictableIdleTimeMillis
                if (checkTime && i < checkCount) { //留尾部的minIdle個連接先不銷毀
                    evictConnections[evictCount++] = connection;
                    continue;
                } else if (idleMillis > maxEvictableIdleTimeMillis) { //尾部的minIdle個連接如果連接空閑時間>maxEvictableIdleTimeMillis,也會被銷毀
                    evictConnections[evictCount++] = connection;
                    continue;
                }
            }
 
            if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) { //如果連接空閑時間未超過minEvictableIdleTimeMillis,但超過了keepAliveBetweenTimeMillis就要進行探活
                keepAliveConnections[keepAliveCount++] = connection;
            }
        }
 
    } finally {
        lock.unlock();
    }
 
    if (evictCount > 0) { //如果有需要銷毀的,則進行關閉連接操作。
        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) { //如果有需要探測的,則進行探測。探活的,則放回可用池;探不活的,直接關閉,并通知創建。
        // keep order
        for (int i = keepAliveCount - 1; i >= 0; --i) {
            //此處省略一萬行
        }
    }
 
    if (needFill) {
        lock.lock();
        try {
            int fillCount = minIdle - (activeCount + poolingCount + createTaskCount); //需要補充創建的連接個數
            for (int i = 0; i < fillCount; ++i) {
                emptySignal(); //給CreateConnectionThread發送empty條件信號來創建連接
            }
        } finally {
            lock.unlock();
        }
    }
}

3.3.2 removeAbandoned()連接泄漏檢測

連接泄露檢查,打開removeAbandoned功能,連接從連接池借出后,長時間不歸還,將觸發強制關閉其staement并歸還。回收周期隨timeBetweenEvictionRunsMillis進行,如果連接借出時間起超過removeAbandonedTimeout,則強制關閉其staement并歸還。對性能會有一些影響,建議懷疑存在泄漏之后再打開,不建議在生產環境中使用。

//強制歸還泄漏(借出后長時間未歸還)的連接
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); //連接借出時間
 
            if (timeMillis >= removeAbandonedTimeoutMillis) {
                iter.remove();
                abandonedList.add(pooledConnection);
            }
        }
    } finally {
        activeConnectionLock.unlock();
    }
 
    if (abandonedList.size() > 0) {
        for (DruidPooledConnection pooledConnection : abandonedList) {
            JdbcUtils.close(pooledConnection); //強制歸還
        }
    }
 
    return removeCount;
}

4.獲取連接流程getConnection()

圖片圖片

連接池最核心的功能就是連接的獲取與回收。我們直接看getConnectionDirect(),它負責獲取一個可用的連接。

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
    for (;;) { //如果某次獲取到的連接無效,一般會丟棄該連接并重新獲取。
        DruidPooledConnection poolableConnection;
        try {
            poolableConnection = getConnectionInternal(maxWaitMillis); //獲取連接
        } catch (GetConnectionTimeoutException ex) {
            //......
        }
 
        if (testOnBorrow) { //如果testOnBorrow=true,則進行探測。
            boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
            if (!validate) {
                discardConnection(poolableConnection.holder); //探測失敗,則丟棄此連接并重新獲取。
                continue;
            }
        } else {
            if (testWhileIdle) { //如果testWhileIdle=true且空閑時間>timeBetweenEvictionRunsMillis,則進行探測。
                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); //如果探測失敗,則丟棄連接并重新獲取。
                         continue;
                    }
                }
            }
        }
 
        //是否打開連接泄露檢查。DestroyConnectionThread如果檢測到連接借出時間超過removeAbandonedTimeout,則強制歸還連接到連接池中。
        //對性能會有一些影響,建議懷疑存在泄漏之后再打開,不建議在生產環境中使用。
        if (removeAbandoned) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); //保存發起方的線程棧
            poolableConnection.connectStackTrace = stackTrace;
            poolableConnection.setConnectedTimeNano(); //重置借出時間
 
            activeConnectionLock.lock();
            try {
                activeConnections.put(poolableConnection, PRESENT); //放進activeConnections
            } finally {
                activeConnectionLock.unlock();
            }
        }
 
        return poolableConnection;
    }
}

4.1 getConnectionInternal()獲取一個連接

getConnectionInternal()負責從連接池獲取一個連接(但該連接并不保證可用),并包裝成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) { //如果等待獲取連接的線程數超過maxWaitThreadCount,則拋出異常
                throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                        + lock.getQueueLength());
            }
 
             //從可用連接池里獲取連接,如果沒有則阻塞等待。
            if (maxWait > 0) {
                holder = pollLast(nanos);
            } else {
                holder = takeLast();
            }
 
            //......
        } //......
 
        break;
    }
 
    //......
    holder.incrementUseCount(); //連接的使用次數+1
 
    DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); ////包裝成一個DruidPooledConnection對象
    return poolalbeConnection;
}

4.2 takeLast()阻塞等待尾部連接

//阻塞等待尾部連接
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
    try {
        while (poolingCount == 0) {  //如果沒有可用連接,就發送個empty條件信號給CreateConnectionThread,并等待notEmpty條件信號
            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;
}

至此,向請求線程返回一個可用的連接DruidPooledConnection。

5.執行&異常處理

圖片圖片

如下為Mybatis執行SQL的核心函數(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); //下調DruidPooledPreparedStatement.execute()執行SQL
            unwrapped = result;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            //......
            throw (Throwable)unwrapped; //如果發生異常,繼續上拋
        } finally {
            if (sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); //關閉連接,對應Druid是歸還。
            }
 
        }
 
        return unwrapped;
    }
}
  • method.invoke()會通過Druid獲取連接,并調用DruidPooledPreparedStatement.execute();
  • 執行結束后,close連接,此時會觸發Druid的連接歸還;
  • 執行中如果發生異常,繼續向上拋。

5.1 DruidPooledPreparedStatement.execute()

此時,進入Druid連接中statement的execute(),如果發生異常進入checkException()。

public boolean execute() throws SQLException {
    conn.beforeExecute();
    try {
        return stmt.execute(); //執行SQL
    } catch (Throwable t) {
        errorCheck(t);
 
        throw checkException(t); //如果發生異常,調用DruidDataSource.handleConnectionException()對連接進行處理,并繼續上拋
    } 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);
    }
}
//對致命異常進行處理
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) { //關閉連接內的所有的statement
                    JdbcUtils.close(stmt);
                }
            } finally {
            }
        }
 
        emptySignalCalled = this.discardConnection(holder); //銷毀
    }
    LOG.error("{conn-" + (holder != null ? holder.getConnectionId() : "null") + "} discard", error);
 
    //......
}

6.回收連接流程recycle()

圖片圖片

使用DruidPooledConnection連接進行SQL操作后,會調用DruidPooledConnection.recycle()進行回收操作。

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) { //限制連接的最大使用次數。超過此值,會被直接關閉。
            discardConnection(holder);
            return;
        }
 
        if (testOnReturn) { //如果testOnReturn=true,歸還前也檢測下
            //......
        }
 
        final long currentTimeMillis = System.currentTimeMillis();
        if (phyTimeoutMillis > 0) { //檢測物理存活時間
            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 將連接放入可用連接池尾部,并發送notEmpty條件信號

//將連接放入可用連接池尾部,并發送notEmpty條件信號
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
    if (poolingCount >= maxActive || e.discard) {
        return false;
    }
 
    e.lastActiveTimeMillis = lastActiveTimeMillis; //重置最近活躍時間
    connections[poolingCount] = e; //歸還到尾部
    incrementPoolingCount();
 
    notEmpty.signal();
    notEmptySignalCount++;
 
    return true;
}

至此,連接已成功回收。

7.總結

7.1 整個連接池的核心操作

  • init()初始化:1)創建initialSize個連接;2)啟動LogStatsThread、CreateConnectionThread、DestroyConnectionThread三個線程;
  • getConnection()獲取連接:獲取后會從連接池移除,Connection只能歸當前線程所用;
  • recycle()回收連接:放回連接池后,其他線程就可以再次獲取該連接重復利用了。

7.2 條件信號協作

  • 獲取連接時:如果連接池里沒有連接,會發出empty條件信號,并等待notEmpty條件信號。CreateConnectionThread收到empty信號后,如果滿足條件則創建一個新連接,也會發出notEmpty條件信號;如果不滿足,則忽略此次empty信號。
  • 回收連接時:連接放回連接池后,會發出notEmpty條件信號。如果有請求在阻塞等待獲取連接,此時會被喚醒,從而獲取連接。

7.3 幾處檢測和銷毀邏輯

  • 借出時:

如果testOnBorrow,則探測;

如果(testWhileIdle = true && 空閑時間 > timeBetweenEvictionRunsMillis)則進行探測;

  • 執行時:
  • 如果拋出異常且exceptionSorter判斷是致命異常,就調用handleFatalError()進行銷毀;
  • 歸還時:
  • 如果連接使用次數超過phyMaxUseCount,則銷毀;
  • 如果testOnReturn=true,則探測;
  • 如果連接建立時間走過phyTimeoutMillis,則銷毀;
  • DestroyConnectionThread每隔timeBetweenEvictionRunsMillis掃描一次連接池中的空閑連接:
  • 如果物理存活時間超過phyTimeoutMillis,則銷毀;
  • 如果( keepAlive && 空閑時間 >= keepAliveBetweenTimeMillis),則進行探測;
  • 如果空閑時間 >= minEvictableIdleTimeMillis,則銷毀(但要保證留下minIdle個);而如果空閑時間超過maxEvictableIdleTimeMillis則必須銷毀;
  • 如果( removeAbandoned == true && 連接借出時間 > removeAbandonedTimeoutMillis),則被強制關閉其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" />
  
    <!-- 獲取連接的等待超時時間,單位是毫秒。配置了maxWait之后,缺省啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 -->
    <property name="maxWait" value="60000" />
 
    <!-- 探測的SQL -->
    <property name="validationQuery" value="SELECT 1" />
    <!-- 獲取連接時,執行validationQuery檢測連接是否有效 -->
    <property name="testOnBorrow" value="false" />
    <!-- 獲取連接時,如果空閑時間超過timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效 -->
    <property name="testWhileIdle" value="true" />
    <!-- 歸還連接時,執行validationQuery檢測連接是否有效 -->
    <property name="testOnReturn" value="false" />
  
    <!-- 定期檢測的時間間隔,單位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000" />
    <!-- 定期檢測時,如果空閑時間超過此值則進行銷毀(但要保證留下minIdle個連接),單位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000" />
</bean>

此配置說明:

  • 線程池剛啟動時會創建1個(initialSize)連接,隨著程序的運行,池不忙時也會保持最少3個(minIdle)空閑連接,但總連接數(包括空閑和在用)不超過20個(maxActive);
  • 獲取連接時:

如果連接池沒有空閑連接,最長等待60秒(maxWait);

【主動】如果獲取到的連接空閑時間大于60秒(timeBetweenEvictionRunsMillis),則執行validationQuery檢測連接是否還有效(有效則使用,無效則銷毀);

  • 執行SQL時:
  • 【被動】如果發生致命異常(默認exceptionSorter=MySqlExceptionSorter,如CommunicationsException),則銷毀該連接;
  • DestroyConnectionThread每隔60秒(timeBetweenEvictionRunsMillis)掃描一次連接池中的空閑連接:
  • 【主動】如果空閑時間超過300秒(minEvictableIdleTimeMillis),則銷毀(但要保證留下minIdle=3個);而如果空閑時間超過7小時(maxEvictableIdleTimeMillis默認為7小時)則必須銷毀。

9.監控

Druid通過SPI開放了很多擴展點,我們架構部基于此封裝了監控組件,直接上報到Prometheus。效果如下:

圖片圖片

圖片圖片

關于作者

杜云杰,高級架構師,轉轉架構部負責人,轉轉技術委員會執行主席,騰訊云MVP。負責服務治理、MQ、云平臺、APM、IM、分布式調用鏈路追蹤、監控系統、配置中心、分布式任務調度平臺、分布式ID生成器、分布式鎖等基礎組件。微信號:waterystone,歡迎建設性交流。

責任編輯:武曉燕 來源: 轉轉技術
相關推薦

2025-01-16 10:30:49

2009-08-24 15:48:53

Java連接池

2009-06-17 09:59:46

Hibernate 連

2009-09-22 16:04:50

Hibernate連接

2009-06-17 16:22:45

Hibernate連接

2024-12-04 15:55:19

2022-02-21 07:48:54

Mysql數據庫SpringBoot

2010-05-17 16:38:08

MySQL 連接池

2011-05-13 09:34:51

TomcatMysql連接池

2019-11-27 10:31:51

數據庫連接池內存

2019-12-30 15:30:13

連接池請求PHP

2023-12-11 08:32:58

數據庫DruidDBA

2025-02-07 12:11:52

2022-05-13 07:31:58

數據庫連接池druid

2011-05-19 09:53:33

數據庫連接池

2010-11-08 16:46:57

2011-09-08 10:30:42

Druid數據庫連接池

2009-06-16 09:25:31

JBoss配置

2009-07-09 17:36:44

JDBC連接池配置

2010-06-25 10:36:27

Java連接池
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天操操操操操 | 在线视频91 | 成人亚洲综合 | 日韩精品视频在线播放 | 久久久精品一区二区 | 农村真人裸体丰满少妇毛片 | 国内精品视频在线观看 | 亚洲国产中文字幕 | 亚洲综合在线一区 | a级大片免费观看 | 91精品国产综合久久久亚洲 | 日韩中文字幕高清 | 欧美激情a∨在线视频播放 成人免费共享视频 | 欧美极品在线观看 | 日本黄色大片免费 | 国产探花 | 男女搞网站 | 日本免费在线看 | 精品少妇一区二区三区在线播放 | 久草.com| 中国毛片免费 | 国产一区二区三区在线视频 | 精品国产一二三区 | 亚洲视频免费在线观看 | 欧美综合国产精品久久丁香 | 成人欧美在线 | 中文字幕视频在线观看 | 久久久91精品国产一区二区三区 | 久久久久久久综合 | 日本一二三区在线观看 | 色橹橹欧美在线观看视频高清 | 天天操夜夜操 | 欧美日韩在线免费 | 免费在线观看一级毛片 | 在线观看特色大片免费网站 | 中文在线一区二区 | 亚洲另类自拍 | 免费毛片www com cn | 日日干天天操 | 久久av一区 | 国产情侣在线看 |