對於共用資源,有一個很著名的設計模式:資源集區(Resource Pool)。該模式正是為瞭解決資源的頻繁分配﹑釋放所造成的問題。為解決我們的問題,可以採用資料庫連接池技術。資料庫連接池的基本思想就是為資料庫連接建立一個“緩衝池”。預先在緩衝池中放入一定數量的串連,當需要建立資料庫連接時,只需從“緩衝池”中取出一個,使用完畢之後再放回去。我們可以通過設定串連池最大串連數來防止系統無盡的與資料庫連接。更為重要的是我們可以通過串連池的管理機制監視資料庫的串連的數量﹑使用方式,為系統開發﹑測試及效能調整提供依據。
為什麼使用串連池
串連,是我們的程式設計語言與資料庫互動的一種方式。我們經常會聽到這麼一句話“資料庫連接很昂貴“。
有人接受這種說法,卻不知道它的真正含義。因此,下面通過執行個體解釋它究竟是什麼。
下面是Mysql資料庫建立串連的的一段代碼:
String connUrl ="jdbc:mysql://your.database.domain/yourDBname";Class.forName("com.mysql.jdbc.Driver");Connection con =DriverManager.getConnection (connUrl);
當我們建立了一個Connection對象,它在內部都執行了什麼:
1.“DriverManager”檢查並註冊驅動程式;
2.“com.mysql.jdbc.Driver”就是我們註冊了的驅動程式,它會在驅動程式類中調用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根據我們請求的“connUrl”,建立一個“Socket串連”,串連到IP為“your.database.domain”,預設連接埠3306的資料庫。
4.建立的Socket串連將被用來查詢我們指定的資料庫,並最終讓程式返回得到一個結果。
簡單的擷取一個串連,系統卻要在背後做很多消耗資源的事情,大多時候,建立串連的時間比執行sql語句的時間還要長。
傳統的擷取串連方式如所示:
使用者每次請求都需要向資料庫獲得連結,而資料庫建立串連通常需要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,資料庫伺服器就需要建立10萬次串連,極大的浪費資料庫的資源,並且極易造成資料庫伺服器記憶體溢出、拓機。
採用串連池技術後的過程如下:
資料庫連接是一種關鍵的有限的昂貴的資源,這一點在多使用者的網頁應用程式中體現的尤為突出。對資料庫連接的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的效能指標。資料庫連接池負責分配,管理和釋放資料庫連接,它允許應用程式重複使用一個現有的資料庫連接,而不是重建立立一個。
需要注意的問題
1、並發問題
為了使串連管理服務具有最大的通用性,必須考慮多線程環境,即並發問題。這個問題相對比較好解決,因為各個語言自身提供了對並發管理的支援像java,c#等等,使用synchronized(java)、lock(C#)關鍵字即可確保線程是同步的。
2、交易處理
我們知道,事務具有原子性,此時要求對資料庫的操作符合“ALL-OR-NOTHING”原則,即對於一組SQL語句要麼全做,要麼全不做。
我們知道當2個線程公用一個串連Connection對象,而且各自都有自己的事務要處理時候,對於串連池是一個很頭疼的問題,因為即使Connection類提供了相應的事務支援,可是我們仍然不能確定那個資料庫操作是對應那個事務的,這是由於我們有2個線程都在進行事務操作而引起的。為此我們可以使用每一個事務獨佔一個串連來實現,雖然這種方法有點浪費串連池資源但是可以大大降低交易管理的複雜性。
3、串連池的分配與釋放
串連池的分配與釋放,對系統的效能有很大的影響。合理的分配與釋放,可以提高串連的複用度,從而降低建立新串連的開銷,同時還可以加快使用者的訪問速度。
對於串連的管理可使用一個List。即把已經建立的串連都放入List中去統一管理。每當使用者請求一個串連時,系統檢查這個List中有沒有可以分配的串連。如果有就把那個最合適的串連分配給他(如何能找到最合適的串連文章將在關鍵議題中指出);如果沒有就拋出一個異常給使用者,List中串連是否可以被分配由一個線程來專門管理。
4、串連池的配置與維護
串連池中到底應該放置多少串連,才能使系統的效能最佳?系統可採取設定最小串連數(minConnection)和最大串連數(maxConnection)等參數來控制串連池中的串連。比方說,最小串連數是系統啟動時串連池所建立的串連數。如果建立過多,則系統啟動就慢,但建立後系統的響應速度會很快;如果建立過少,則系統啟動的很快,響應起來卻慢。這樣,可以在開發時,設定較小的最小串連數,開發起來會快,而在系統實際使用時設定較大的,因為這樣對訪問客戶來說速度會快些。最大串連數是串連池中允許串連的最大數目,具體設定多少,要看系統的訪問量,可通過軟體需求上得到。
如何確保串連池中的最小串連數呢?有動態和靜態兩種策略。動態即每隔一定時間就對串連池進行檢測,如果發現串連數量小於最小串連數,則補充相應數量的新串連,以保證串連池的正常運轉。靜態是發現空閑串連不夠時再去檢查。
Tomcat串連池
Tomcat預設使用的是DBCP資料庫連接池,其實從本質上講,Tomcat是利用Apache Commons DBCP來實現的,只不過把特定的功能整合到了tomcat-dbcp.jar包中。
使用法法如下:
步驟1:
在Tomcat中Context.xml中添加
<!-- path表示網站的訪問方式 --><!-- 例:http://localhost:8080/test 配置為/test --><!-- docBase="fileLocation" 應用儲存的實際路徑,沒有的話則從webapps目錄找 --><!-- Context標籤內的這些屬性都可以省略不寫,使用預設的設定 --><Context path="/TomcatDbPools" docBase="TomcatDbPools" debug="0" reloadable="true"> <!-- 使用DBCP配置的資料來源 --><Resource <!-- 指定資源集區的Resource的JNDI的名字,就是給串連池起的名字 --> name="jdbc/mysql_connect" <!-- 系統管理權限,指定管理Resource的Manager,可以是Container或Application --> auth="Container" <!--指出Resource所屬的類名,是什麼類型的資料來源--> type="javax.sql.DataSource" <!-- 資料庫驅動類 --> driverClassName="com.mysql.jdbc.Driver" <!-- 資料庫連接url--> url=" jdbc:mysql://localhost:3306/test" <!-- 資料庫使用者名稱 --> username="admin" <!-- 資料庫密碼 --> password="123456" <!-- 串連池最大啟用的串連數,設為0表示無限制--> maxActive="100" <!-- 串連池中最多可閒置串連數 --> maxIdle="30" <!-- 為串連最大的等待時間,單位毫秒,如果超過此時間將接到異常。設為-1表示無限制--> maxWait="10000" /> </context>
註:還可以用minIdle配置串連池中最少空閑maxIdle個串連,用initialSize配置初始化串連數目。可同時配置多個資料來源。
如果在Tomcat的server.xml檔案中配置資料來源,有兩種方法都可以實現:
方法1:將上面的配置內容直接添加在<Host>節點下。
方法2:在<GlobalNamingResources>節點下添加:
<GlobalNamingResources><!-- 這裡的factory指的是該Resource 配置使用的是哪個資料來源配置類,這裡使用的是tomcat內建的標準資料來源Resource配置類,--><!-- 這個類也可以自己寫,實現javax.naming.spi.ObjectFactory 介面即可。 --><!-- 某些地方使用的commons-dbcp.jar中的org.apache.commons.dbcp.BasicDataSourceFactory,--><!-- 如果使用這個就需把commons-dbcp.jar及其依賴的jar包,都放在tomcat的lib下,光放在工程的WEB-INF/lib下是不夠的。 --><Resource name="mysql_connect"factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"maxActive="100" maxIdle="30" maxWait="10000" name="jdbc/TomcatDbPool1" password="123456" type="javax.sql.DataSource" url="jdbc:mysql://localhost:3306/test" username="root"/> </GlobalNamingResources>
然後在context.xml檔案中的<Context></Context>節點中加入如下內容:
<ResourceLink name="jdbc/mysql_connect" global="mysql_connect" type="javax.sql.DataSource"/>
在server.xml中配置的資料來源是全域的,所有項目都可以使用。全域的resource只是為了重用,方便所有該tomcat下的web工程的資料來源管理,但如果你的tomcat不會同時載入多個web工程,也就是說一個tomcat只載入一個web工程時,是沒有必要配置全域的resource的。
此外,還需要將mysql的Java驅動類以及其他依賴包(如果有)放到tomcat的lib目錄下。
步驟2:
在web.xml中,配置<resource-ref>元素以在web應用中引用JNDI資源。
<resource-ref> <!-- 對該資源的描述語言 --> <description> dbcpconnect</description> <!-- 引用的資源名,必須與Context.xml中的名字一致 --> <res-ref-name> jdbc/mysql_connect </res-ref-name> <!-- 資源類型 --> <res-type>javax.sql.DataSource</res-type> <!-- 系統管理權限 --> <res-auth>Container</res-auth></resource-ref>
步驟3:
在Web應用中使用資料來源
//獲得對資料來源的引用:Context ctx =new InitialContext();//java:comp/env/是java中JNDI固定寫法。DataSource ds =(DataSource) ctx.lookup("java:comp/env/jdbc/mysql_connect ");//獲得資料庫連接對象:Connection conn= ds.getConnection();//返回資料庫連接到串連池:conn.close();
DBCP串連池
DBCP 是 Apache 軟體基金組織下的開源串連池實現,要使用DBCP資料來源,需要應用程式應在系統中增加如下兩個 jar 檔案:
Commons-dbcp.jar:串連池的實現
Commons-pool.jar:串連池實現的依賴庫
Tomcat 的串連池正是採用該串連池來實現的。該資料庫連接池既可以與應用伺服器整合使用,也可由應用程式獨立使用。
步驟1:
在類目錄下加入dbcp的設定檔:dbcp.properties
#資料庫驅動driverClassName=com.mysql.jdbc.Driver#資料庫連接地址url=jdbc:mysql://localhost/test#使用者名稱username=root#密碼password=123456#串連池的最大資料庫連接數。設為0表示無限制maxActive=30#最大空閑數,資料庫連接的最大空閑時間。超過空閑時間,資料庫連#接將被標記為不可用,然後被釋放。設為0表示無限制maxIdle=10#最大建立串連等待時間。如果超過此時間將接到異常。設為-1表示無限制maxWait=1000#超過removeAbandonedTimeout時間後,是否進行沒用串連(廢棄)的回收(預設為false,調整為true)removeAbandoned=true#超過時間限制,回收沒有用(廢棄)的串連(預設為 300秒)removeAbandonedTimeout=180
步驟2:
在擷取資料庫連接的工具類(如jdbcUtils)的靜態代碼塊中建立池:
import java.io.InputStream;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.Properties;import javax.sql.DataSource;import org.apache.commons.dbcp.BasicDataSourceFactory; /** * 在java中,編寫資料庫連接池需實現java.sql.DataSource介面,每一種資料庫連接池都是DataSource介面的實現 * DBCP串連池就是java.sql.DataSource介面的一個具體實現 */public classJdbcUtils_DBCP { private static DataSource ds = null; //在靜態代碼塊中建立資料庫連接池 static{ try{ //載入dbcp.properties設定檔 InputStream in =JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties prop = new Properties(); prop.load(in); //建立資料來源 ds =BasicDataSourceFactory.createDataSource(prop); }catch (Exception e) { throw newExceptionInInitializerError(e); } } //從資料來源中擷取資料庫連接 public static Connection getConnection()throws SQLException{ //從資料來源中擷取資料庫連接 return ds.getConnection(); } //釋放串連 public static void release(Connection conn){ if(conn!=null){ try{ //將Connection連線物件還給資料庫連接池 conn.close(); }catch (Exception e) { e.printStackTrace(); } } }}
步驟3:
在應用中擷取串連
Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try{ //擷取資料庫連接 conn =JdbcUtils_DBCP.getConnection(); …… }catch (Exception e) { e.printStackTrace(); }finally{ //釋放資源 JdbcUtils_DBCP.release(conn); }
C3P0串連池
c3p0是一個開源的JDBC串連池,它實現了資料來源和JNDI綁定,支援JDBC3規範和JDBC2的標準擴充。c3p0一般是與Hibernate,Spring等架構一塊使用的,當然也可以單獨使用。
dbcp沒有自動回收空閑串連的功能,c3p0有自動回收空閑串連功能。
使用c3p0需要匯入c3p0.jar、mchange-commons-.jar,如果操作的是Oracle資料庫,那麼還需要匯入c3p0-oracle-thin-extras-pre1.jar。
步驟1:
在類目錄下加入C3P0的設定檔:c3p0-config.xml
<c3p0-config> <!-- C3P0的預設(預設)配置,--> <!-- 如果在代碼中“ComboPooledDataSourceds = new ComboPooledDataSource();”這樣寫就表示使用的是C3P0的預設(預設)配置資訊來建立資料來源 --> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/anysearch</property> <property name="user">root</property> <property name="password">123456</property> <!--當串連池中的串連耗盡的時候c3p0一次同時擷取的串連數。Default:3 --> <property name="acquireIncrement">5</property> <!--初始化的串連數,取值應在minPoolSize與maxPoolSize之間。Default: 3--> <property name="initialPoolSize">10</property> <!--串連池中保留的最小串連數--> <property name="minPoolSize">5</property> <!--串連池中保留的最大串連數。Default:15 --> <property name="maxPoolSize">20</property> <!--定義在從資料庫擷取新串連失敗後重複嘗試的次數。Default: 30 --> <property name="acquireRetryAttempts">30</property> <!--兩次串連中間隔時間,單位毫秒。Default: 1000 --> <property name="acquireRetryDelay">1000</property> <!--串連關閉時預設將所有未提交的操作復原。Default: false --> <property name="autoCommitOnClose">false</property> </default-config> <!-- C3P0的命名配置,--> <!-- 如果在代碼中“ComboPooledDataSourceds = new ComboPooledDataSource("MySQL");”這樣寫就表示使用的是name是MySQL的配置資訊來建立資料來源 --> <named-config name="MySQL"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test2</property> <property name="user">root</property> <property name="password">123456</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config> </c3p0-config>
還有更多可設定的參數,具體可查閱相關資料。
步驟2:
在擷取資料庫連接的工具類(如jdbcUtils)的靜態代碼塊中建立池
import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import com.mchange.v2.c3p0.ComboPooledDataSource; public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds =null; //在靜態代碼塊中建立資料庫連接池 static{ try{ //通過代碼建立C3P0資料庫連接池 /*ds = new ComboPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/test"); ds.setUser("root"); ds.setPassword("123456"); ds.setInitialPoolSize(10); ds.setMinPoolSize(5); ds.setMaxPoolSize(20);*/ //通過讀取C3P0的xml設定檔建立資料來源,C3P0的xml設定檔c3p0-config.xml必須放在src目錄下 //ds = newComboPooledDataSource();//使用C3P0的預設配置來建立資料來源 ds = newComboPooledDataSource("MySQL");//使用C3P0的命名配置來建立資料來源 }catch (Exception e) { throw newExceptionInInitializerError(e); } } //從資料來源中擷取資料庫連接 public static Connection getConnection()throws SQLException{ //從資料來源中擷取資料庫連接 return ds.getConnection(); } //釋放連結 public static void release(Connection conn){ if(conn!=null){ try{ //將Connection連線物件還給資料庫連接池 conn.close(); }catch (Exception e) { e.printStackTrace(); } } }}
步驟3:
在應用中擷取串連
Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try{ //擷取資料庫連接 conn = JdbcUtils_C3P0.getConnection(); …… }catch (Exception e) { e.printStackTrace(); }finally{ //釋放資源 JdbcUtils_C3P0release(conn); }
其他串連池
此外,還有其他的串連池可供選擇,比如使用比較廣泛的Proxool。Proxool是一種Java資料庫連接池技術。是sourceforge下的一個開源項目,這個項目提供一個健壯、易用的串連池,最為關鍵的是這個串連池提供監控的功能,方便易用,便於發現串連泄漏的情況。
proxool和 c3p0能夠更好的支援高並發,但是在穩定性方面略遜於dpcp。
可根據項目的實際需要來選擇串連池。