數據庫鏈接池原理詳解與自定義鏈接池實現

實現原理

數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中,這些數據庫鏈接的數量是由最小數據庫鏈接數制約。不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量。鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中。 
鏈接池基本的思想是在系統初始化的時候,將數據庫鏈接做爲對象存儲在內存中,當用戶須要訪問數據庫時,並不是創建一個新的鏈接,而是從鏈接池中取出一個已創建的空閒鏈接對象。使用完畢後,用戶也並不是將鏈接關閉,而是將鏈接放回鏈接池中,以供下一個請求訪問使用。而鏈接的創建、斷開都由鏈接池自身來管理。同時,還能夠經過設置鏈接池的參數來控制鏈接池中的初始鏈接數、鏈接的上下限數以及每一個鏈接的最大使用次數、最大空閒時間等等。也能夠經過其自身的管理機制來監視數據庫鏈接的數量、使用狀況等。java

注意事項

一、數據庫鏈接池的最小鏈接數是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費。 
二、數據庫鏈接池的最大鏈接數是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過此數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響以後的數據庫操做。 
三、最大鏈接數具體值要看系統的訪問量.要通過不斷測試取一個平衡值 
四、隔一段時間對鏈接池進行檢測,發現小於最小鏈接數的則補充相應數量的新鏈接 
五、最小鏈接數與最大鏈接數差距,最小鏈接數與最大鏈接數相差太大,那麼最早的鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫鏈接。不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,它將被放到鏈接池中等待重複使用或是空閒超時後被釋放。sql

數據庫鏈接池配置屬性

目前數據庫鏈接池種類繁多,不一樣種類基本的配置屬性大同小異,例如c3p0、Proxool、DDConnectionBroker、DBPool、XAPool、Druid、dbcp,這裏咱們以dbcp爲例說說主要的配置項:數據庫

#最大鏈接數量:鏈接池在同一時間可以分配的最大活動鏈接的數量,,若是設置爲非正數則表示不限制,默認值8
maxActive=15
#最小空閒鏈接:鏈接池中允許保持空閒狀態的最小鏈接數量,低於這個數量將建立新的鏈接,若是設置爲0則不建立,默認值0
minIdle=5
#最大空閒鏈接:鏈接池中允許保持空閒狀態的最大鏈接數量,超過的空閒鏈接將被釋放,若是設置爲負數表示不限制,默認值8
maxIdle=10
#初始化鏈接數:鏈接池啓動時建立的初始化鏈接數量,默認值0
initialSize=5
#鏈接被泄露時是否打印
logAbandoned=true
#是否自動回收超時鏈接
removeAbandoned=true 
#超時時間(以秒數爲單位)
removeAbandonedTimeout=180
# 最大等待時間:當沒有可用鏈接時,鏈接池等待鏈接被歸還的最大時間(以毫秒計數),超過期間則拋出異常,若是設置爲-1表示無限等待,默認值無限
maxWait=3000
#在空閒鏈接回收器線程運行期間休眠的時間值(以毫秒爲單位).
timeBetweenEvictionRunsMillis=10000
#在每次空閒鏈接回收器線程(若是有)運行時檢查的鏈接數量
numTestsPerEvictionRun=8
#鏈接在池中保持空閒而不被空閒鏈接回收器線程
minEvictableIdleTimeMillis=10000
#用來驗證從鏈接池取出的鏈接
validationQuery=SELECT 1
#指明是否在從池中取出鏈接前進行檢驗
testOnBorrow=true
#testOnReturn  false  指明是否在歸還到池中前進行檢驗
testOnReturn=true
#設置爲true後若是要生效,validationQuery參數必須設置爲非空字符串
testWhileIdle

自定義數據庫鏈接池示例

 首先看一下鏈接池的定義。它經過構造函數初始化鏈接的最大上限,經過一個雙向隊列來維護鏈接,調用方須要先調用fetchConnection(long)方法來指定在多少毫秒內超時獲取鏈接,當鏈接使用完成後,須要調用releaseConnection(Connection)方法將鏈接放回線程池ide

public class ConnectionPool {
    private LinkedList<Connection> pool = new LinkedList<Connection>();

    /**
     * 初始化鏈接池的大小
     * @param initialSize
     */
    public ConnectionPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(ConnectionDriver.createConnection());
            }
        }
    }

    /**
     * 釋放鏈接,放回到鏈接池
     * @param connection
     */
    public void releaseConnection(Connection connection){
        if(connection != null){
            synchronized (pool) {
                // 鏈接釋放後須要進行通知,這樣其餘消費者可以感知到鏈接池中已經歸還了一個鏈接
                pool.addLast(connection);
                pool.notifyAll();
            }
        }
    }

    /**
     * 在mills內沒法獲取到鏈接,將會返回null
     * @param mills
     * @return
     * @throws InterruptedException
     */
    public Connection fetchConnection(long mills) throws InterruptedException{
        synchronized (pool) {
            // 無限制等待
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    pool.wait();
                }
                return pool.removeFirst();
            }else{
                long future = System.currentTimeMillis() + mills;
                long remaining = mills;
                while (pool.isEmpty() && remaining > 0) {
                    // 等待超時
                    pool.wait(remaining);
                    remaining = future - System.currentTimeMillis();
                }
                Connection result = null;
                if (!pool.isEmpty()) {
                    result = pool.removeFirst();
                }
                return result;
            }
        }
    }
}

因爲java.sql.Connection是一個接口,最終的實現是由數據庫驅動提供方來實現的,考慮到只是個示例,咱們經過動態代理構造了一個Connection,該Connection的代理實現僅僅是在commit()方法調用時休眠100毫秒函數

public class ConnectionDriver {
    static class ConnectionHandler implements InvocationHandler{
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.equals("commit")){
                TimeUnit.MILLISECONDS.sleep(100);
            }
            return null;
        }
    }

    /**
     * 建立一個Connection的代理,在commit時休眠100毫秒
     * @return
     */
    public static final Connection createConnection(){
        return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),
                new Class[] { Connection.class },new ConnectionHandler());
    }
}

下面經過一個示例來測試簡易數據庫鏈接池的工做狀況,模擬客戶端ConnectionRunner獲取、使用、最後釋放鏈接的過程,當它使用時鏈接將會增長獲取到鏈接的數量,反之,將會增長未獲取到鏈接的數量測試

public class ConnectionPoolTest {
    static ConnectionPool pool = new ConnectionPool(10);
    // 保證全部ConnectionRunner可以同時開始
    static CountDownLatch start = new CountDownLatch(1);
    // main線程將會等待全部ConnectionRunner結束後才能繼續執行
    static CountDownLatch end;
    public static void main(String[] args) {
        // 線程數量,能夠修改線程數量進行觀察
        int threadCount = 10;
        end = new CountDownLatch(threadCount);
        int count = 20;
        AtomicInteger got = new AtomicInteger();
        AtomicInteger notGot = new AtomicInteger();
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new ConnetionRunner(count, got, notGot), "ConnectionRunnerThread");
            thread.start();
        }
        start.countDown();
        try {
            end.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("total invoke: " + (threadCount * count));
        System.out.println("got connection: " + got);
        System.out.println("not got connection " + notGot);
    }

    static class ConnetionRunner implements Runnable {

        int count;
        AtomicInteger got;
        AtomicInteger notGot;
        public ConnetionRunner(int count, AtomicInteger got, AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }
        @Override
        public void run() {
            try {
                start.await();
            } catch (Exception ex) {
            }
            while (count > 0) {
                try {
                    // 從線程池中獲取鏈接,若是1000ms內沒法獲取到,將會返回null
                    // 分別統計鏈接獲取的數量got和未獲取到的數量notGot
                    Connection connection = pool.fetchConnection(1);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                    }
                } catch (Exception ex) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }

    }

}

CountDownLatch類是一個同步計數器,構造時傳入int參數,該參數就是計數器的初始值,每調用一次countDown()方法,計數器減1,計數器大於0 時,await()方法會阻塞程序繼續執行CountDownLatch如其所寫,是一個倒計數的鎖存器,當計數減至0時觸發特定的事件。利用這種特性,可讓主線程等待子線程的結束。這這裏保證讓全部的ConnetionRunner 
都執行完再執行main進行打印。fetch

運行結果: 
20個客戶端大數據

total invoke: 200
got connection: 200
not got connection 0

50個客戶端ui

total invoke: 1000
got connection: 999
not got connection 1

100個客戶端this

total invoke: 2000
got connection: 1842
not got connection 158

在資源必定的狀況下(鏈接池中的10個鏈接),隨着客戶端線程的逐步增長,客戶端出現超時沒法獲取鏈接的比率不斷升高。雖然客戶端線程在這種超時獲取的模式下會出現鏈接沒法獲取的狀況,可是它可以保證客戶端線程不會一直掛在鏈接獲取的操做上,而是「按時」返回,並告知客戶端鏈接獲取出現問題,是系統的一種自我保護機制。數據庫鏈接池的設計也能夠複用到其餘的資源獲取的場景,針對昂貴資源(好比數據庫鏈接)的獲取都應該加以超時限制。

轉載:http://blog.csdn.net/fuyuwei2015/article/details/72419975

相關文章
相關標籤/搜索