1. 程式人生 > >從Connection Reset問題淺談DBCP的使用技巧

從Connection Reset問題淺談DBCP的使用技巧

我們大家在做J2EE專案開發的時候,都會用到Application Server,然後配置Connection Pool,Data Source,但不知道大家有沒有留意到,其實我們絕大部分的應用用的都是Apache的DBCP機制。 JES,Weblogic,JBoss等等的大型App Server,其中一個好處就是提供了Admin Console,讓配置做起來就像傻瓜式的,Step By Step就可以了,下面舉個用Tomcat的應用例子,深入一點探討DBCP的配置都做了些什麼。(當然得配置Server.xml了,但是其實JES和Weblogic等等的大型App Server,也是可以同樣修改Server.xml或這Domain.xml來達到同一目的的,只不過有了Admin Console,大家容易避免犯錯,但其實我覺得,要深入瞭解一個App Server,避免不了深入瞭解配置檔案裡面的內容)。 當使用DBCP(通常我們都是用Oracle的了)時候,不知道大家有沒有遇到一個情況,當資料庫連線因為某種原因斷掉(有可能時網路問題,導致App Server跑了一天後,第二天再跑馬上爆錯誤),再從Connection Pool中獲取連線而又不做Validate,這時候取得的Connection實際上已經是無效的了,從而導致程式一跑,馬上爆Connect Reset錯誤。 其實只要你瞭解一下DBCP的運作機制和相關屬性的話,這個問題就很容易避免了。 DBCP使用Apache的ObjectPool作為Connection Pool的實現,在構造GenericObjectPool的時候,會生成一個Inner Class Evictor,實現Runnable的介面。如果屬性_timeBetweenEvictionRunsMillis > 0,每過_timeBetweenEvictionRunsMillis毫秒後Evictor會呼叫evict method,檢查Object的idle time是否大於屬性_minEvictableIdleTimeMillis毫秒(如果_minEvictableIdleTimeMillis設定為<=0則忽略,使用default value 30分鐘),如果是則銷燬該Object,否則就啟用並進行Validate,然後呼叫ensureMinIdle method檢查確保Connection Pool中的Object個數不小於屬性_minIdle。在呼叫returnObject method把Object放回ObjectPool時候,需要檢查該Object是否有效,然後呼叫PoolableObjectFactory的passivateObject method使Object處於inactive狀態,再檢查ObjectPool中的物件個數是否小於屬性_maxIdle,是則可以把該Object放回到ObjectPool,否則銷燬此Object。 除此之外,還有幾個比較重要的屬性,_testOnBorrow,_testOnReturn,_testWhileIdle,這些屬性的意思是取得,返回物件,空閒時候是否進行Valiadte,檢查物件是否有效。預設都為False,只有把這些屬性設為True,再提供_validationQuery語句就可以保證DBCP始終有效了,例如,Oracle中就完全可以使用select 1 from dual來進行驗證,這裡要注意的是,DBCP要求_validationQuery語句查詢的Result Set必須為非空。 在Tomcat的Server.xml,我們可以看看下面的這個例子: <Resource name="lda/raw"
              type="javax.sql.DataSource"
               password="lda_master"
               driverClassName="oracle.jdbc.driver.OracleDriver"
               maxIdle="30" minIdle="2" maxWait="60000" maxActive="1000"
               testOnBorrow="true" testWhileIdle="true" validationQuery="select 1 from dual"
               username="lda_master" url="jdbc:oracle:thin:@192.160.100.107:15537:lcststd"/> 這樣一來,就能夠解決Connect Reset的問題了。剛才說了,其實很多App Server都會有相應的配置地方,只是大型的伺服器正好提供了Admin Console,上面可以顯式的配置Connection Pool,也有明顯的屬性選擇,這裡就不一一詳述了,都是眼見的功夫。 ============================================================================================== 網上很多評論說DBCP有很多BUG,但是都沒有指明是什麼BUG,只有一部分人說資料庫如果因為某種原因斷掉後再DBCP取道的連線都是失效的連線,而沒有重新取。有的時候會報Io 異常:Connection reset。

解決方法:

spring中datasource的配置如下:
    <bean id="dispatchdataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
    <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:myserver" />
    <property name="username" value="user1" />
    <property name="password" value="pwd" />
    <property name="maxActive" value="10000" />
    <property name="maxIdle" value="30" />
     <property name="minIdle" value="2" />
    <property name="maxWait" value="600000" />
    <property name="testOnBorrow" value="true"/>
    <property name="testWhileIdle" value="true"/>
    <property name="validationQuery" value="select 1 from dual"/>
</bean>

分析:

DBCP使用apache的物件池ObjectPool作為連線池的實現,有以下主要的方法

Object borrowObject() throws Exception;從物件池取得一個有效物件

void returnObject(Object obj) throws Exception;使用完的物件放回物件池

void invalidateObject(Object obj) throws Exception;使物件失效

void addObject() throws Exception;生成一個新物件


ObjectPool的一個實現就是GenericObjectPool,這個類使用物件工廠PoolableObjectFactory實現物件的生成,失效檢查等等功能,以其實現資料庫連線工廠PoolableConnectionFactory做以說明,主要方法:

     Object makeObject() throws Exception; 使用ConnectionFactory生成新連線

     void destroyObject(Object obj) throws Exception;關閉連線

     boolean validateObject(Object obj); 驗證連線是否有效,如果_validationQuery不空,則使用該屬性作為驗證連線是否有效的sql語句,查詢資料庫

     void activateObject(Object obj) throws Exception;啟用連線物件

     void passivateObject(Object obj) throws Exception; 關閉連線生成過的Statement和ResultSet,使連線處於非活動狀態

    而GenericObjectPool有幾個主要屬性

     _timeBetweenEvictionRunsMillis:失效檢查執行緒執行時間間隔,預設-1

     _maxIdle:物件池中物件最大個數

     _minIdle:物件池中物件最小個數

     _maxActive:可以從物件池中取出的物件最大個數,為0則表示沒有限制,預設為8

     在構造GenericObjectPool時,會生成一個內嵌類Evictor,實現自Runnable介面。如果 _timeBetweenEvictionRunsMillis大於0,每過_timeBetweenEvictionRunsMillis毫秒 Evictor會呼叫evict()方法,檢查物件的閒置時間是否大於 _minEvictableIdleTimeMillis毫秒(_minEvictableIdleTimeMillis小於等於0時則忽略,預設為30 分鐘),是則銷燬此物件,否則就啟用並校驗物件,然後呼叫ensureMinIdle方法檢查確保池中物件個數不小於_minIdle。在呼叫 returnObject方法把物件放回物件池,首先檢查該物件是否有效,然後呼叫PoolableObjectFactory 的passivateObject方法使物件處於非活動狀態。再檢查物件池中物件個數是否小於_maxIdle,是則可以把此物件放回物件池,否則銷燬此物件。

     還有幾個很重要的屬性,_testOnBorrow、_testOnReturn、_testWhileIdle,這些屬性的意義是取得、返回物件和空閒時是否進行驗證,檢查物件是否有效,預設都為false即不驗證。所以當使用DBCP時,資料庫連線因為某種原因斷掉後,再從連線池中取得連線又不進行驗證,這時取得的連線實際已經時無效的資料庫連線了。網上很多說 DBCP的bug應該都是如此吧,只有把這些屬性設為true,再提供_validationQuery語句就可以保證資料庫連線始終有效了,oracle資料庫可以使用SELECT COUNT(*) FROM DUAL,不過DBCP要求_validationQuery語句查詢的記錄集必須不為空,可能這也可以算一個小小的BUG,其實只要_validationQuery語句執行通過就可以了。