您當前的位置:首頁 > 曲藝

資料來源連線池的原理及Tomcat中的應用

作者:由 chainho 發表于 曲藝時間:2016-06-23

資料來源連線池的原理及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 getDrivers() {

java。util。Vector result = new 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 makeObject() throws Exception {

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> idleObjects;

我們再看連線的關閉,

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是這樣工作的!

重定向與轉發的本質區別

怎樣閱讀原始碼