Tomcat JDBC pool源碼部析

 最近跟隨Tomcat7.0開發了一個JDBC 鏈接池。html

 Svn: http://svn.apache.org/repos/asf/tomcat/trunk/modules/jdbc-pool 算法

http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html上大篇幅的介紹,包括基本的使用指南。本篇從源碼的角度,分析該鏈接池的實現思路。apache

應用使用JDBC鏈接也,主要關注如下三個方面:tomcat

1.  獲取鏈接併發

2.  歸還鏈接app

3.空閒鏈接關閉。異步

一. 獲取鏈接 async

ConnectionPool提供三個接口用於獲取鏈接:ide

  
  
           
  
  
  1. public Future<Connection> getConnectionAsync() throws SQLException { 
  2.  
  3.      try { 
  4.  
  5.          PooledConnection pc = borrowConnection(0nullnull); 
  6.  
  7.          if (pc!=null) { 
  8.  
  9.              return new ConnectionFuture(pc); 
  10.  
  11.          } 
  12.  
  13.      }catch (SQLException x) { 
  14.  
  15.          if (x.getMessage().indexOf("NoWait")<0) { 
  16.  
  17.              throw x; 
  18.  
  19.          } 
  20.  
  21.      } 
  22.  
  23. /we can only retrieve a future if the underlying queue supports it. 
  24.  
  25.      if (idle instanceof FairBlockingQueue<?>) { 
  26.  
  27.          Future<PooledConnection> pcf = ((FairBlockingQueue<PooledConnection>)idle).pollAsync(); 
  28.  
  29.          return new ConnectionFuture(pcf); 
  30.  
  31.      } else if (idle instanceof MultiLockFairBlockingQueue<?>) { 
  32.  
  33.              Future<PooledConnection> pcf = ((MultiLockFairBlockingQueue<PooledConnection>)idle).pollAsync(); 
  34.  
  35.              return new ConnectionFuture(pcf); 
  36.  
  37.      } else { 
  38.  
  39.          throw new SQLException("Connection pool is misconfigured, doesn't support async retrieval. Set the 'fair' property to 'true'"); 
  40.  
  41.      } 
  42.  
  43.  
  44.  
  45. public Connection getConnection() throws SQLException { 
  46.  
  47.      //check out a connection 
  48.  
  49.      PooledConnection con = borrowConnection(-1,null,null); 
  50.  
  51.      return setupConnection(con); 
  52.  
  53.  } 
  54.  
  55. public Connection getConnection(String username, String password)    throws SQLException { 
  56.  
  57.      // check out a connection 
  58.  
  59.     PooledConnection con = borrowConnection(-1, username,      password); 
  60.  
  61.      return setupConnection(con); 
  62.  
  63.  } 

第一個方法:getConnectionAsync用於獲取一個鏈接的Feature.它用於支持以異步的方式獲取鏈接。後兩個方法不一樣之處就是傳遞了所需鏈接的用戶名與密碼。咱們這裏得點分析第三個方法.svn

PooledConnection con = borrowConnection(-1, username, password);

borrowConnection方法從空閒隊列中獲取一個鏈接,或新建一個鏈接。看一下源碼:

  
  
           
  
  
  1. /** 
  2.  
  3.  * Thread safe way to retrieve a connection from the pool 
  4.  
  5.   * @param wait - time to wait, overrides the maxWait from the properties, 
  6.  
  7.   * set to -1 if you wish to use maxWait, 0 if you wish no wait time. 
  8.  
  9.      * @return PooledConnection 
  10.  
  11.      * @throws SQLException 
  12.  
  13.      */ 
  14.  
  15. private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException { 
  16.  
  17.     //若是鏈接被關閉則直接拋出異常 
  18.  
  19.         if (isClosed()) { 
  20.  
  21.             throw new SQLException("Connection pool closed."); 
  22.  
  23.         } //end if 
  24.  
  25.   
  26.  
  27.         //get the current time stamp 
  28.  
  29.         long now = System.currentTimeMillis(); 
  30.  
  31.         //see if there is one available immediately 
  32.  
  33.        /*從空閒隊列中獲取一個鏈接。 其實idle裏存在鏈接對象有的可能並無 
  34.  
  35. 綁定物理鏈接。這也是Tomcat jdbc pool的一個特別,鏈接在將要被使用時, 
  36.  
  37. 纔會初始化*/ 
  38.  
  39. PooledConnection con = idle.poll(); 
  40.  
  41.         while (true) { 
  42.  
  43.             if (con!=null) { 
  44.  
  45.                 //configure the connection and return it 
  46.  
  47.                 /*這裏又出現一個borrowConnection的重載方法。該方法對從空閒隊列中取到的鏈接對象進行配置和驗證,稍後評述*/ 
  48.  
  49.                 PooledConnection result = borrowConnection(now, con, username, password); 
  50.  
  51.                 //null should never be returned, but was in a previous impl. 
  52.  
  53.                 // null should never be returned這句註釋不對,根據後面的代碼 
  54.  
  55.                 // 來看,null是有可能發生。 
  56.  
  57.                 if (result!=nullreturn result; 
  58.  
  59.             } 
  60.  
  61.             //if we get here, see if we need to create one 
  62.  
  63.             //this is not 100% accurate since it doesn't use a shared 
  64.  
  65.             //atomic variable - a connection can become idle while we are creating 
  66.  
  67.             //a new connection 
  68.  
  69.             /*從上面的英文註釋咱們很明白,當執行到這裏時,惟一的多是idle隊列沒能取到鏈接對象。
  70. 若是條件容許,咱們將建立新的鏈接.在這裏做者用了一個特別的算法,也是tomcat代碼中經常使用的,
  71. 咱們姑且稱他佔位法(我通常這麼叫)。這個算法的特色就是先在計數器Size中佔一個位置
  72. (Size是原子變量。可以解決併發問題)。即size+1.而後檢查size有沒有超標。若是超標
  73. 則減去剛纔新加的1。不然建立一個新的鏈接。不過這裏我注意的是,若是建立新鏈接時失敗,
  74. size也必須減1。其實與大學時的用書搶位子殊途同歸。*/ 
  75.  
  76.             if (size.get() < getPoolProperties().getMaxActive()) { 
  77.  
  78.                 //atomic duplicate check 
  79.  
  80.                 if (size.addAndGet(1) > getPoolProperties().getMaxActive()) { 
  81.  
  82.                   //if we got here, two threads passed through the first if 
  83.  
  84.                     size.decrementAndGet(); 
  85.  
  86.                 } else { 
  87.  
  88.                     //create a connection, we're below the limit 
  89.  
  90.                     //後面再描述這個方法。 
  91.  
  92.                     return createConnection(now, con, username, password); 
  93.  
  94.                 } 
  95.  
  96.             } //end if 
  97.  
  98.         //到這裏則表示鏈接池已滿,不能建立新的鏈接,咱們只能等待其餘線程釋放的鏈接 
  99.  
  100.             //calculate wait time for this iteration 
  101.  
  102.             long maxWait = wait; 
  103.  
  104. //if the passed in wait time is -1, 
  105.  
  106. //means we should use the pool property value 
  107.  
  108.             if (wait==-1) { 
  109.  
  110.                 maxWait = (getPoolProperties().getMaxWait()<=0)?Long.MAX_VALUE:getPoolProperties().getMaxWait(); 
  111.  
  112.             } 
  113.  
  114.            //咱們須要計算本次最多能容忍的等待。爲何要計算呢。由於咱們可能中間被假//喚醒但卻沒能拿到鏈接。 
  115.  
  116. long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now)); 
  117.  
  118.             waitcount.incrementAndGet(); 
  119.  
  120.             try { 
  121.  
  122.                 //retrieve an existing connection 
  123.  
  124.                 con = idle.poll(timetowait, TimeUnit.MILLISECONDS); 
  125.  
  126.             } catch (InterruptedException ex) { 
  127.  
  128.                 if (getPoolProperties().getPropagateInterruptState()) { 
  129.  
  130.                     Thread.currentThread().interrupt(); 
  131.  
  132.                 } else { 
  133.  
  134.                     Thread.interrupted(); 
  135.  
  136.                 } 
  137.  
  138.                 SQLException sx = new SQLException("Pool wait interrupted."); 
  139.  
  140.                 sx.initCause(ex); 
  141.  
  142.                 throw sx; 
  143.  
  144.             } finally { 
  145.  
  146.                 waitcount.decrementAndGet(); 
  147.  
  148.             } 
  149.  
  150.             //no wait, return one if we have one 
  151.  
  152.             if (maxWait==0 && con == null) { 
  153.  
  154.                 throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " + 
  155.  
  156.                 "NoWait: Pool empty. Unable to fetch a connection, none available["+busy.size()+" in use]."); 
  157.  
  158.             } 
  159.  
  160.             //we didn't get a connection, let’s see if we timed out 
  161.  
  162.             if (con == null) { 
  163.  
  164.                 … 
  165.  
  166.               if ((System.currentTimeMillis() - now) >= maxWait) { 
  167.  
  168.                     throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " + 
  169.  
  170.                         "Timeout: Pool empty. Unable to fetch a connection in " + (maxWait / 1000) + 
  171.  
  172.                         " seconds, none available[size:"+size.get() +"; busy:"+busy.size()+"; idle:"+idle.size()+"; lastwait:"+timetowait+"]."); 
  173.  
  174.                 } else { 
  175.  
  176.                     //no timeout, lets try again 
  177.  
  178.                     //若是沒有超時,咱們繼續去獲取鏈接。 
  179.  
  180.                     continue
  181.  
  182.                 } 
  183.  
  184.             } 
  185.  
  186.         } //while 
  187.  
  188.     } 

waitTime表示鏈接請求者容忍等待的最大時間,超時沒有獲取到鏈接則拋出PoolExhaustedException異常。OK

下面咱們看中間遇到的borrowConnection的重載方法:

protected PooledConnection borrowConnection(long now, PooledConnection con, String username, String password)

protected PooledConnection createConnection(long now, PooledConnection notUsed, String username, String password)

首先看第一個:

  
  
           
  
  
  1. /** 
  2.      * Validates and configures a previously idle connection 
  3.  
  4.      * @param now - timestamp 
  5.  
  6.      * @param con - the connection to validate and configure 
  7.  
  8.      * @return con 
  9.  
  10.      * @throws SQLException if a validation error happens 
  11.  
  12.      */ 
  13.  
  14.  protected PooledConnection borrowConnection(long now, PooledConnection con, String username, String password) throws SQLException { 
  15.  
  16.         //we have a connection, lets set it up 
  17.  
  18.         //flag to see if we need to nullify 
  19.  
  20.         boolean setToNull = false
  21.  
  22.         try { 
  23.  
  24.             //爲當前鏈接加鎖
  25.  
  26.             con.lock(); 
  27.  
  28.            //驗證當前鏈接用用戶名與密碼是否符合需求 
  29.  
  30.             boolean usercheck = con.checkUser(username, password); 
  31.  
  32.             if (con.isReleased()) { 
  33.  
  34.                 return null
  35.  
  36.             } 
  37.  
  38.             //對於沒標記爲丟棄的鏈接且沒有初始化的鏈接進行初始化。 
  39.  
  40.             if (!con.isDiscarded() && !con.isInitialized()) { 
  41.  
  42.                 //attempt to connect 
  43.  
  44.                 try { 
  45.  
  46.                     con.connect(); 
  47.  
  48.                 } catch (Exception x) { 
  49.  
  50.                     release(con); 
  51.  
  52.                     setToNull = true
  53.  
  54.                     if (x instanceof SQLException) { 
  55.  
  56.                         throw (SQLException)x; 
  57.  
  58.                     } else { 
  59.  
  60.                         SQLException ex  = new SQLException(x.getMessage()); 
  61.  
  62.                         ex.initCause(x); 
  63.  
  64.                         throw ex; 
  65.  
  66.                     } 
  67.  
  68.                 } 
  69.  
  70.             } 
  71.  
  72.             
  73.  
  74.             if (usercheck) { 
  75.  
  76.                 if ((!con.isDiscarded()) && con.validate(PooledConnection.VALIDATE_BORROW)) { 
  77.  
  78.                     //set the timestamp 
  79.  
  80.                     con.setTimestamp(now); 
  81.  
  82.                     //這裏添加LogAbandoned的功能是爲了在檢測到鏈接泄露時, 
  83.  
  84. //獲取佔用該鏈接的線程棧 
  85.  
  86.                     if (getPoolProperties().isLogAbandoned()) { 
  87.  
  88.                         //set the stack trace for this pool 
  89.  
  90.                         con.setStackTrace(getThreadDump()); 
  91.  
  92.                     } 
  93.  
  94.                //放入busy隊列。若是不成功,則該鏈接將沒法被追蹤。(這種狀況不會出現) 
  95.  
  96.                     if (!busy.offer(con)) { 
  97.  
  98.                         log.debug("Connection doesn't fit into busy array, connection will not be traceable."); 
  99.  
  100.                     } 
  101.  
  102.                     return con; 
  103.  
  104.                 } 
  105.  
  106.             } 
  107.  
  108.             //if we reached here, that means the connection 
  109.  
  110.             //is either has another principal, is discarded or validation failed. 
  111.  
  112.             //we will make one more attempt 
  113.  
  114.             //in order to guarantee that the thread that just acquired 
  115.  
  116.             //the connection shouldn't have to poll again. 
  117.  
  118.             //這裏英語描述的很清楚了。若是鏈接的用戶名不符,被丟棄或驗證失敗, 
  119.  
  120.             //咱們能夠重連該鏈接,以知足需求,而不是再去獲取其餘的。 
  121.  
  122.             try { 
  123.  
  124.                 con.reconnect(); 
  125.  
  126.                 if (con.validate(PooledConnection.VALIDATE_INIT)) { 
  127.  
  128.                     //set the timestamp 
  129.  
  130.                     con.setTimestamp(now); 
  131.  
  132.                     if (getPoolProperties().isLogAbandoned()) { 
  133.  
  134.                         //set the stack trace for this pool 
  135.  
  136.                         con.setStackTrace(getThreadDump()); 
  137.  
  138.                     } 
  139.  
  140.                     if (!busy.offer(con)) { 
  141.  
  142.                         log.debug("Connection doesn't fit into busy array, connection will not be traceable."); 
  143.  
  144.                     } 
  145.  
  146.                     return con; 
  147.  
  148.                 } else { 
  149.  
  150.                     //validation failed. 
  151.  
  152.                     release(con); 
  153.  
  154.                     setToNull = true
  155.  
  156.                     throw new SQLException("Failed to validate a newly established connection."); 
  157.  
  158.                 } 
  159.  
  160.             } catch (Exception x) { 
  161.  
  162.                 release(con); 
  163.  
  164.                 setToNull = true
  165.  
  166.                 if (x instanceof SQLException) { 
  167.  
  168.                     throw (SQLException)x; 
  169.  
  170.                 } else { 
  171.  
  172.                     SQLException ex  = new SQLException(x.getMessage()); 
  173.  
  174.                     ex.initCause(x); 
  175.  
  176.                     throw ex; 
  177.  
  178.                 } 
  179.  
  180.             } 
  181.  
  182.         } finally { 
  183.  
  184.             con.unlock(); 
  185.  
  186.             if (setToNull) { 
  187.  
  188.                 con = null
  189.  
  190.             } 
  191.  
  192.         } 
  193.  
  194.     } 

(待續)

相關文章
相關標籤/搜索