|
最近在做一個(gè)內(nèi)部測(cè)試工具類的優(yōu)化工作中接觸到了連接池, 對(duì)象池技術(shù), 將原有的未使用連接池的數(shù)據(jù)庫訪問操作改成連接池方式.性能有了非常大的提升, 事實(shí)證明, 經(jīng)過兩次改造, 原來一個(gè)比較大的測(cè)試類需要500多秒, 第一次優(yōu)化后只需要300多秒, 第二次改用連接池之后同一個(gè)測(cè)試類只需要80多秒.下面是改造過程中的一些總結(jié).
對(duì)象池就是以"空間換時(shí)間"的一種常用緩存機(jī)制, 這里的"時(shí)間"特指創(chuàng)建時(shí)間,因此這也給出了對(duì)象池的適用范圍:如果一種對(duì)象的創(chuàng)建過程非常耗時(shí)的話, 那么請(qǐng)使用對(duì)象池. 內(nèi)部原理簡單的說, 就是將創(chuàng)建的對(duì)象放到一個(gè)容器中, 用完之后不是銷毀而是再放回該容器, 讓其他的對(duì)象調(diào)用, 對(duì)象池中還涉及到一些高級(jí)的技術(shù), 比如過期銷毀, 被破壞時(shí)銷毀, 對(duì)象數(shù)超過池大小銷毀, 對(duì)象池中沒有可用空閑對(duì)象時(shí)等待等等. apache的common-pool工具庫是對(duì)池化技術(shù)原理的一種具體實(shí)現(xiàn). 在闡述原來之前, 這里先理解幾個(gè)概念: 對(duì)象池(ObjectPool接口): 可以把它認(rèn)為是一種容器, 它是用來裝池對(duì)象的, 并且包含了用來創(chuàng)建池對(duì)象的工廠對(duì)象 池對(duì)象:就是要放到池容器中的對(duì)象, 理論上可以是任何對(duì)象. 對(duì)象池工廠(ObjectPoolFactory接口):用來創(chuàng)建對(duì)象池的工廠, 這個(gè)沒什么好說的. 池對(duì)象工廠(PoolableObjectFactory接口):用來創(chuàng)建池對(duì)象, 將不用的池對(duì)象進(jìn)行鈍化(passivateObject), 對(duì)要使用的池對(duì)象進(jìn)行激活(activeObject), 對(duì)池對(duì)象進(jìn)行驗(yàn)證(validateObject), 對(duì)有問題的池對(duì)象進(jìn)行銷毀(destroyObject)等工作 對(duì)象池中封裝了創(chuàng)建, 獲取, 歸還, 銷毀池對(duì)象的職責(zé), 當(dāng)然這些工作都是通過池對(duì)象工廠來實(shí)施的, 容器內(nèi)部還有一個(gè)或多個(gè)用來盛池對(duì)象的容器.對(duì)象池會(huì)對(duì)容器大小, 存放時(shí)間, 訪問等待時(shí)間, 空閑時(shí)間等等進(jìn)行一些控制, 因?yàn)榭梢愿鶕?jù)需要來調(diào)整這些設(shè)置. 當(dāng)需要拿一個(gè)池對(duì)象的時(shí)候, 就從容器中取出一個(gè), 如果容器中沒有的話, 而且又沒有達(dá)到容器的最大限制, 那么就調(diào)用池對(duì)象工廠, 新建一個(gè)池對(duì)象, 并調(diào)用工廠的激活方法, 對(duì)創(chuàng)建的對(duì)象進(jìn)行激活, 驗(yàn)證等一系列操作. 如果已經(jīng)達(dá)到池容器的最大值, 而對(duì)象池中又經(jīng)沒有空閑的對(duì)象, 那么將會(huì)繼續(xù)等待, 直到有新的空閑的對(duì)象被丟進(jìn)來, 當(dāng)然這個(gè)等待也是有限度的, 如果超出了這個(gè)限度, 對(duì)象池就會(huì)拋出異常. "出來混, 總是要還的", 池對(duì)象也是如此, 當(dāng)將用完的池對(duì)象歸還到對(duì)象池中的時(shí)候, 對(duì)象池會(huì)調(diào)用池對(duì)象工廠對(duì)該池對(duì)象進(jìn)行驗(yàn)證, 如果驗(yàn)證不通過則被認(rèn)為是有問題的對(duì)象, 將會(huì)被銷毀, 同樣如果容器已經(jīng)滿了, 這個(gè)歸還池對(duì)象將變的"無家可歸", 也會(huì)被銷毀, 如果不屬于上面兩種情況, 對(duì)象池就會(huì)調(diào)用工廠對(duì)象將其鈍化并放入容器中. 在整個(gè)過程中, 激活, 檢查, 鈍化處理都不是必須的, 因此我們?cè)趯?shí)現(xiàn)PoolableObjectFactory接口的時(shí)候, 一般不作處理, 給空實(shí)現(xiàn)即可, 所以誕生了BasePoolableObjectFactory. 當(dāng)然你也可以將要已有的對(duì)象創(chuàng)建好, 然后通過addObject放到對(duì)象池中去, 以備后用. 為了確保對(duì)對(duì)象池的訪問都是線程安全的, 所有對(duì)容器的操作都必須放在synchronized中. 在apache的common-pool工具庫中有5種對(duì)象池:GenericObjectPool和GenericKeyedObjectPool, SoftReferenceObjectPool, StackObjectPool, StackKeyedObjectPool. 五種對(duì)象池可分為兩類, 一類是無key的: ![]() 另一類是有key的: ![]() 前面兩種用CursorableLinkedList來做容器, SoftReferenceObjectPool用ArrayList做容器, 一次性創(chuàng)建所有池化對(duì)象, 并對(duì)容器中的對(duì)象進(jìn)行了軟引用(SoftReference)處理, 從而保證在內(nèi)存充足的時(shí)候池對(duì)象不會(huì)輕易被jvm垃圾回收, 從而具有很強(qiáng)的緩存能力. 最后兩種用Stack做容器. 不帶key的對(duì)象池是對(duì)前面池技術(shù)原理的一種簡單實(shí)現(xiàn), 帶key的相對(duì)復(fù)雜一些, 它會(huì)將池對(duì)象按照key來進(jìn)行分類, 具有相同的key被劃分到一組類別中, 因此有多少個(gè)key, 就會(huì)有多少個(gè)容器. 之所以需要帶key的這種對(duì)象池, 是因?yàn)槠胀ǖ膶?duì)象池通過makeObject()方法創(chuàng)建的對(duì)象基本上都是一模一樣的, 因?yàn)闆]法傳遞參數(shù)來對(duì)池對(duì)象進(jìn)行定制. 因此四種池對(duì)象的區(qū)別主要體現(xiàn)在內(nèi)部的容器的區(qū)別, Stack遵循"后進(jìn)先出"的原則并能保證線程安全, CursorableLinkedList是一個(gè)內(nèi)部用游標(biāo)(cursor)來定位當(dāng)前元素的雙向鏈表, 是非線程安全的, 但是能滿足對(duì)容器的并發(fā)修改.ArrayList是非線程安全的, 便利方便的容器. 使用對(duì)象池的一般步驟:創(chuàng)建一個(gè)池對(duì)象工廠, 將該工廠注入到對(duì)象池中, 當(dāng)要取池對(duì)象, 調(diào)用borrowObject, 當(dāng)要?dú)w還池對(duì)象時(shí), 調(diào)用returnObject, 銷毀池對(duì)象調(diào)用clear(), 如果要連池對(duì)象工廠也一起銷毀, 則調(diào)用close(). 下面是一些時(shí)序圖: borrowObject: ![]() returnObject: ![]() invalidateObject: ![]() apache的連接池工具庫common-dbcp是common-pool在數(shù)據(jù)庫訪問方面的一個(gè)具體應(yīng)用.當(dāng)對(duì)common-pool熟悉之后, 對(duì)common-dbcp就很好理解了. 它通過對(duì)已有的Connection, Statment對(duì)象包裝成池對(duì)象PoolableConnection, PoolablePreparedStatement. 然后在這些池化的對(duì)象中, 持有一個(gè)對(duì)對(duì)象池的引用, 在關(guān)閉的時(shí)候, 不進(jìn)行真正的關(guān)閉處理, 而是通過調(diào)用:
_pool.returnObject(this); 或:
_pool.returnObject(_key,this); 這樣一句, 將連接對(duì)象放回連接池中. 而對(duì)應(yīng)的對(duì)象池前者采用的是ObjectPool, 后者是KeyedObjectPool, 因?yàn)橐粋€(gè)數(shù)據(jù)庫只對(duì)應(yīng)一個(gè)連接, 而執(zhí)行操作的Statement卻根據(jù)Sql的不同會(huì)分很多種. 因此需要根據(jù)sql語句的不同多次進(jìn)行緩存 在對(duì)連接池的管理上, common-dbcp主要采用兩種對(duì)象: 一個(gè)是PoolingDriver, 另一個(gè)是PoolingDataSource, 二者的區(qū)別是PoolingDriver是一個(gè)更底層的操作類, 它持有一個(gè)連接池映射列表, 一般針對(duì)在一個(gè)jvm中要連接多個(gè)數(shù)據(jù)庫, 而后者相對(duì)簡單一些. 內(nèi)部只能持有一個(gè)連接池, 即一個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè)連接池. 下面是common-dbcp的結(jié)構(gòu)關(guān)系: ![]() 下面是參考了common-dbcp的例子之后寫的一個(gè)從連接池中獲取連接的工具類
/**
* 創(chuàng)建連接
*
* @since 2009-1-22 下午02:58:35
*/
public class ConnectionUtils {
// 一些common-dbcp內(nèi)部定義的protocol
private static final String POOL_DRIVER_KEY = "jdbc:apache:commons:dbcp:";
private static final String POLLING_DRIVER = "org.apache.commons.dbcp.PoolingDriver";
/**
* 取得池化驅(qū)動(dòng)器
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
private static PoolingDriver getPoolDriver() throws ClassNotFoundException,
SQLException {
Class.forName(POLLING_DRIVER);
return (PoolingDriver) DriverManager.getDriver(POOL_DRIVER_KEY);
}
/**
* 銷毀所有連接
*
* @throws Exception
*/
public static void destory() throws Exception {
PoolingDriver driver = getPoolDriver();
String[] names = driver.getPoolNames();
for (String name : names) {
driver.getConnectionPool(name).close();
}
}
/**
* 從連接池中獲取數(shù)據(jù)庫連接
*/
public static Connection getConnection(TableMetaData table)
throws Exception {
String key = table.getConnectionKey();
PoolingDriver driver = getPoolDriver();
ObjectPool pool = null;
// 這里找不到連接池會(huì)拋異常, 需要catch一下
try {
pool = driver.getConnectionPool(key);
} catch (Exception e) {
}
if (pool == null) {
// 根據(jù)數(shù)據(jù)庫類型構(gòu)建連接工廠
ConnectionFactory connectionFactory = null;
if (table.getDbAddr() != null
&& TableMetaData.DB_TYPE_MYSQL == table.getDbType()) {
Class.forName(TableMetaData.MYSQL_DRIVER);
connectionFactory = new DriverManagerConnectionFactory(table
.getDBUrl(), null);
} else {
Class.forName(TableMetaData.ORACLE_DRIVER);
connectionFactory = new DriverManagerConnectionFactory(table
.getDBUrl(), table.getDbuser(), table.getDbpass());
}
// 構(gòu)造連接池
ObjectPool connectionPool = new GenericObjectPool(null);
new PoolableConnectionFactory(connectionFactory, connectionPool,
null, null, false, true);
// 將連接池注冊(cè)到driver中
driver.registerPool(key, connectionPool);
}
// 從連接池中拿一個(gè)連接
return DriverManager.getConnection(POOL_DRIVER_KEY + key);
}
}
雖然對(duì)象池技術(shù)在實(shí)際開發(fā)過程中用的不是很多, 但是理解之后對(duì)我們寫程序還是有莫大的好處的, 至少我是這樣的 |
|
|