tomcat-jdbc-pool 實現簡單分析

jopen 9年前發布 | 38K 次閱讀 Tomcat 應用服務器

原文  http://kuang.io/tomcat-jdbc-pool-simple-source/

2015-05-22

什么是連接池?

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

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

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

連接池的作用?

避免多次去創建資源。例如,創建新的數據庫連接,500ms輕輕松松就消耗了。建立TCP連接,數據庫賬號驗證等等。這性能消耗起來,可是非常大的。

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

tomcat-jdbc-pool的特色

基于jdk1.5后的并發實現。代碼簡潔,精練。核心的類就2,3個。

對池的控制就在org.apache.tomcat.jdbc.pool.ConnectionPool中搞定。

</div>

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

對于連接池來說,最基本的特點就是:

  • 有一定的容量,及已經創建好的對象
  • 有「借」有「還」操作的接口
  • </ul>

    池中「借出」連接是怎么個過程?

    在jdbc-pool設計有2隊列,分別為busy和idle,存儲「正在使用」和「空閑」的連接。都采用ArrayBlockingQueue以保證線程安全。

    當有請求「借」的動作過來時,從idle中poll一個連接,然后將該連接再offer至busy隊列中。這是最基本最純凈的思路。

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

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

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

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

    首先池中有維護連接數總量「計數器」(采用AtomicInteger保證線程安全,每次新增或銷毀都會變更)。

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

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

    大致實現代碼如下:

    public class ConnectionPool {
      //連接數的總量
      private AtomicInteger size = new AtomicInteger(0);
      //所有正在使用中的連接
      private BlockingQueue<PooledConnection> busy;
      //所有空閑的連接
      private BlockingQueue<PooledConnection> idle;
      private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException {
        PooledConnection con = idle.poll();
        while (true) {
          if (con != null) {
             try {
               busy.offer(con);//我這簡化了,在源碼中這兒會對連接進行校驗、檢查或進行連接。
             } catch(Exception e) {
             }
             return con;
          }
          if (size.get() < getPoolProperties().getMaxActive()) {
            //占坑神技
            if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
              //既然沒了,那數量也減回去
              //再去等待其他連接歸還回來
              size.decrementAndGet();
            } else {
              return createConnection(now, con, username, password);
            }
          }
          try {
            //檢查并等待新的空閑連接
            con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
          } catch (InterruptedException ex) {
            //....
          } 
        }
      }
    }

    用完后「歸還」連接是怎么個過程?

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

    但是, 做為連接池必須的職責之一,并不真實的斷開與數據庫的連接。 而只是放至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
    }

    當長時間運行后,怎么回收無效的連接?

    這是連接池必備的功能之一,類似檢查死鏈或者釋放自身過多的資源。比如,在高并發過后,對資源消耗量少時,就釋放些不再使用的數據庫連接(真實斷開),維護合理的空格數量。

    看到這應用場景就自然想到,通過后臺線程定時掃描。

    「對的,就是這樣子。」

    同樣在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(); while (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(); //check. check now. } } } public void start() { registerCleaner(this); //并未啟動線程,只是注冊一個清理器 } public void stopRunning() { unregisterCleaner(this); } } }</pre>

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!