資料來源連線池的原理及Tomcat中的應用
資料來源連線池的原理及Tomcat中的應用
本文首發於公眾號「
Tomcat那些事兒
」。本公眾號由曾從事應用伺服器核心研發的工程師維護。文章深入Tomcat原始碼,分析應用伺服器的實現細節,工作原理及與之相關的技術,使用技巧,工作實戰等。起於Tomcat但不止於此。同時會分享併發、JVM等,內容多為原創,歡迎關注。
在Java Web開發過程中,會廣泛使用到
資料來源
。
我們基本的使用方式,是透過Spring使用類似如下的配置,來宣告一個數據源:
在之後應用裡對於資料庫的操作,都基於這個資料來源,但這個
資料來源連線池
的建立、銷燬、管理,對於使用者都是近乎透明的,甚至資料庫連線的獲取,我們都看不到Connection物件了。
這種方式是應用自身的資料庫連線池,各個應用之間互相獨立。
在類似於Tomcat這樣的應用伺服器內部,也有提供資料來源的能力,這時的資料來源,可以為多個應用提供服務。
這一點類似於以前寫過關於Tomcat內部的Connector對於執行緒池的使用,可以各個Connector獨立使用執行緒池,也可以共用配置的Executor。(Tomcat的Connector元件)
那麼,在Tomcat中,怎麼樣配置和使用資料來源呢?
先將對應要使用的資料庫的驅動檔案xx。jar放到TOMCAT_HOME/lib目錄下。
編輯TOMCAT_HOME/conf/context。xml檔案,增加類似於下面的內容:
maxTotal=“100” maxIdle=“30” maxWaitMillis=“10000” username=“root” password=“pwd” driverClassName=“com。mysql。jdbc。Driver” url=“jdbc:mysql://localhost:3306/test”/> 需要提供資料來源的應用內,使用JNDI的方式獲取 Context initContext = new InitialContext(); Context envContext = (Context)initContext。lookup(“java:/comp/env”); DataSource ds = (DataSource)envContext。lookup(“jdbc/TestDB”); Connection conn = ds。getConnection(); 愉快的開始使用資料庫… 我們看,整個過程也並不比使用Spring等框架進行配置複雜,在應用內獲取連線也很容易。多個應用都可以透過第3步的方式獲取資料來源,這使得同時提供多個應用共享資料來源很容易。 這背後的是怎麼實現的呢? 這個容器的連線池是怎麼工作的呢,我們一起來看一看。 在根據context。xml中配置的Resouce初始化的時候,會呼叫具體DataSource對應的實現類,Tomcat內部預設使用的BasicDataSource,在類初始化的時候,會執行這樣一行程式碼DriverManager。getDrivers(),其對應的內容如下,主要作用是使用 java。sql。DriverManager實現的 Service Provider 機制,所有jar檔案包含META-INF/services/java。sql。Driver檔案的,會被自動發現、載入和註冊,不需要在需要獲取連線的時候,再手動的載入和註冊。 public static java。util。Enumeration java。util。Vector for(DriverInfo aDriver : registeredDrivers) { if(isDriverAllowed(aDriver。driver, callerClass)) { result。addElement(aDriver。driver); } else { println(“ skipping: ” + aDriver。getClass()。getName()); } } return (result。elements()); } 之後DataSourceFactory會讀取 Resouce 中指定的資料來源的屬性,建立資料來源。 在我們的應用內getConnection的時候,使用ConnectionFactory建立Connection, 注意在建立Connection的時候,重點程式碼是這個樣子: public PooledObject Connection conn = _connFactory。createConnection(); initializeConnection(conn); PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName); return new DefaultPooledObject<>(pc); 這裡的_pool是GenericObjectPool,連線的獲取是透過其進行的。 public Connection getConnection() throws SQLException { C conn = _pool。borrowObject(); } 在整個pool中包含幾個佇列,其中比較關鍵的一個定義如下: private final LinkedBlockingDeque 我們再看連線的關閉, public void close() throws SQLException { if (getDelegateInternal() != null) { super。close(); super。setDelegate(null); } } 這裡的關閉,並不會真的呼叫到Connection的close方法,我們透過上面的程式碼已經看到,Connection返回的時候,其實是Connection的Wrapper類。在close的時候,真實的會呼叫到下面的程式碼 // Normal close: underlying connection is still open, so we // simply need to return this proxy to the pool try { _pool。returnObject(this); } catch(IllegalStateException e) {} 所謂的return,是把連線放回到上面我們提到的idleObjects佇列中。整個連線是放在一個LIFO的佇列中,所以如果沒有關閉或者超過最大空閒連線,就會加到佇列中。而允許外的連線才會真實的銷燬destory。 int maxIdleSave = getMaxIdle(); if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects。size()) { try { destroy(p); } catch (Exception e) { swallowException(e); } } else { if (getLifo()) { idleObjects。addFirst(p); // 這裡。 } else { idleObjects。addLast(p); } if (isClosed()) { // Pool closed while object was being added to idle objects。 // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } } 總結下:以上是Tomcat內部實現的DataSource部分關鍵程式碼。資料來源我們有時候也稱為 連線池 ,所謂 池 的概念,就是一組可以不斷重用的資源,在使用完畢後,重新恢復狀態,以備再次使用。 而 為了達到重用的效果,對於客戶端的關閉操作,就不能做真實意義上的物理關閉,而是根據池的狀態,以執行具體的入隊重用,還是執行物理關閉 。無論連線池,還是執行緒池,池的原理大致都是這樣的。 相關閱讀 :執行緒池的原理Tomcat的Connector元件和Tomcat學設計模式 | 釋出-訂閱模式 猜你喜歡: 深度揭秘亂碼問題背後的原因及解決方式 WEB應用是怎麼被部署的? 怎樣除錯Tomcat原始碼 IDE裡的Tomcat是這樣工作的! 重定向與轉發的本質區別 怎樣閱讀原始碼
上一篇:你們都是玩了多久的玉米蛇?