1. 程式人生 > >tomcat-jdbc Pool 原始碼實現簡單分析

tomcat-jdbc Pool 原始碼實現簡單分析

=================================

什麼是連線池?

池,不由自主的會想到水池。

小時候,我們都要去遠處的水井挑水,倒進家中的水池裡面。這樣,每次要用水時,直接從水池中「取」就行了。不用大老遠跑去水井打水。

資料庫連線池就如此,我們預先準備好一些連線,放到池中。當需要時,就直接獲取。而不要每次跟資料庫建立一個新的連線。特別對資料庫連線這類耗時,耗資源的操作。當連線用完後,再放回池中,供後續使用。

連線池的作用?

避免多次去建立資源。
例如,建立新的資料庫連線,500ms輕輕鬆鬆就消耗了。建立TCP連線,資料庫賬號驗證等等。這效能消耗起來,可是非常大的。

在稍大的系統內,連線池是必備的。同時,對技術人員要求,對連線池的掌握也是必須的。

tomcat-jdbc-pool的特色

基於jdk1.5後的併發實現。程式碼簡潔,精練。核心的類就2,3個。
對池的控制就在 org.apache.tomcat.jdbc.pool.ConnectionPool 中搞定。

先前有簡單看過 dbcp1.x, c3p0等等,程式碼量真不少,邏輯複雜。
想熟悉池的設計,可以仔細讀讀tomcat-jdbc-pool,非常快速的入手。在dbcp2的實現時,跟tomcat-jdbc-pool思路一致(完全copy的版本)

對於連線池來說,最基本的特點就是:

  • 有一定的容量,及已經建立好的物件
  • 有「借」有「還」操作的介面

池中「借出」連線是怎麼個過程?

jdbc-pool設計有2佇列,分別為busyidle,儲存「正在使用」和「空閒」的連線。都採用ArrayBlockingQueue以保證執行緒安全。

當有請求「借」的動作過來時,從idlepoll一個連線,然後將該連線再offerbusy佇列中。這是最基本最純淨的思路。

idle連線不夠時,內部會再去建立新的連線返回給客戶端。

但是,做為「池」必須的職責之一是控制總量,不會任你去增長。

那麼,有意思來了,他是怎麼控制總量的咧?

我們可以通俗點稱『佔坑法』(tomcat中也有不少場景採用這方式)。

首先池中有維護連線數總量「計數器」(採用AtomicInteger

保證執行緒安全,每次新增或銷燬都會變更)。

『佔坑法』就在每次要新建立連線池,先總量計數器+1(佔位),再比較是否達到配置的池的最大連線數。如果沒有達到,則建立新的;如果已達到了,則等待現有連線釋放,再取走。

有點類似,大學時先用本書去搶位置佔著。

大致實現程式碼【已經對原始碼簡化】如下:

public class ConnectionPool {

    //連線數的總量
    private AtomicInteger size = new AtomicInteger(0);

    //所有正在使用中的連線
    private BlockingQueue<PooledConnection> busy;

    //所有空閒的連線
    private BlockingQueue<PooledConnection> idle;

        PooledConnection con = idle.poll();//

        while (true) {
            if (con != null) {//如果從空閒連線佇列中取出的連線不為空
                 //把這個連線加入正在使用中的連線列表,***並返回
                 busy.offer(con);//這簡化了,在原始碼中這兒會對連線進行校驗、檢查或進行連線。
                 return con;
            }

            //下面判斷空閒連線佇列中取出的連線為空的情況

            //沒有超出連線總數
            if (size.get() < getPoolProperties().getMaxActive()) {

                //佔坑神技 
                if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {//超出連線總數
                    //既然沒了,那數量也減回去
                    //再去等待其他連線歸還回來
                    size.decrementAndGet();
                } else {
                    //新建一個物理連線
                    return createConnection(now, con, username, password);
                }
            }


            //等待一定時間timetowait後,再次從空閒連線佇列中取出的連線,迴圈執行上面過程
            con = idle.poll(timetowait, TimeUnit.MILLISECONDS);

        }
    }

}

用完後「歸還」連線是怎麼個過程?

大致思路跟「借」操作相反落。當然是無視那些「善後」的工作,只關注資源的管理。

但是,做為連線池必須的職責之一,並不真實的斷開與資料庫的連線。而只是放至idle佇列中,供客戶端下次再使用。如果有需要或必要肯定會釋放,技巧所在。

大致程式碼如下:

protected void returnConnection(PooledConnection con) {
    if (con != null) {
        try {
            con.lock();

            if (busy.remove(con)) {
                //跟允許的最大空閒數比較
                if(idle.size() < poolProperties.getMaxIdle()) {
                    idle.offer(con);
                    //原始碼中呼叫release
                    //會根據配置項執行一些校驗,例如:testOnReturn為true,則在回收時檢查連線是否正常
                    //release(con); 
                }
        } catch(Exception e) {
           //.... 
        } finally {
            con.unlock();
        }
    } //end if
}

當長時間執行後,怎麼回收無效的連線?

這是連線池必備的功能之一,類似檢查死鏈或者釋放自身過多的資源。比如,在高併發過後,對資源消耗量少時,就釋放些不再使用的資料庫連線(真實斷開),維護合理的空格數量。【*** 連線池的minIdle定義了這個數量】

看到這應用場景就自然想到,通過後臺執行緒定時掃描。

「對的,就是這樣子。」

同樣在ConnectionPool這個類檔案中的PoolCleaner類。寫在同個類檔案中,便於用this進行傳遞資料。不用再去構造個複雜的ConnectionPool物件。

直接上程式碼,「好程式碼」就是最好的描述。

public class ConnectionPool {

    /**
     * Initialize the connection pool - called from the constructor
     */
    protected void init(PoolConfiguration properties) throws SQLException {
        initializePoolCleaner(properties);
    }

    public void initializePoolCleaner(PoolConfiguration properties) {        
        if (properties.isPoolSweeperEnabled()) {
            poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis());
            poolCleaner.start(); //只註冊一個清理器,並未啟動執行緒。
        } //end if
    }

    /**
    * 檢查所有的空閒連線
    */
    public void checkIdle(boolean ignoreMinSize) {

        try {
            if (idle.size()==0) return;

            Iterator<PooledConnection> unlocked = idle.iterator();

            //【重點:如果空閒數量大於屬性MinIdle,則執行清理!】
            while ( (idle.size()>=getPoolProperties().getMinIdle())) &&unlocked.hasNext()) {
                PooledConnection con = unlocked.next();
                try {
                    con.lock();
                    //如果這時已到busy中,則不檢查了
                    if (busy.contains(con)) {
                        continue;
                    }

                    release(con);//釋放了物理連線
                    idle.remove(con);//從空閒佇列中移除

                } finally {
                    con.unlock();
                }
            } //while
        } catch (Exception e) {
            //....
        }

    }

    private static volatile Timer poolCleanTimer = null;
    private static HashSet<PoolCleaner> cleaners = new HashSet<>();

    //註冊一個清理器
    private static synchronized void registerCleaner(PoolCleaner cleaner) {
        unregisterCleaner(cleaner);
        cleaners.add(cleaner);

        //一堆構造方式。。。
        if (poolCleanTimer == null) {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            try {
                Thread.currentThread().setContextClassLoader(ConnectionPool.class.getClassLoader());
                poolCleanTimer = new Timer("PoolCleaner["+ System.identityHashCode(ConnectionPool.class.getClassLoader()) + ":"+
                                           System.currentTimeMillis() + "]", true);
            }finally {
                Thread.currentThread().setContextClassLoader(loader);
            }
        }
        //構造定時掃描器
        //java有內庫非常強大,想用啥有啥呀
        poolCleanTimer.scheduleAtFixedRate(cleaner, cleaner.sleepTime,cleaner.sleepTime);
    }

    //真實的處理執行緒在這兒。。。
    protected static class PoolCleaner extends TimerTask {
        protected WeakReference<ConnectionPool> pool;

        PoolCleaner(ConnectionPool pool, long sleepTime) {
            //弱引用,不瞭解的可以google下
            this.pool = new WeakReference<>(pool);
        }

        @Override
        public void run() {
            ConnectionPool pool = this.pool.get();
            if (pool == null) {
                stopRunning();
            } else if (!pool.isClosed()) {                

                if (pool.getPoolProperties().getMinIdle() < pool.idle.size()) {
                    pool.checkIdle(); //【重點,如果空閒數量大於MinIdle,則執行checkIdle】
                }
            }
        }

        public void start() {
            registerCleaner(this); //並未啟動執行緒,只是註冊一個清理器
        }

        public void stopRunning() {
            unregisterCleaner(this);
        }
    }
}