數據庫鏈接池的原理

一.早期咱們怎麼進行數據庫操做html

       1.原理:通常來講,java應用程序訪問數據庫的過程是:java

   ①裝載數據庫驅動程序;web

   ②經過jdbc創建數據庫鏈接;spring

   ③訪問數據庫,執行sql語句;sql

   ④斷開數據庫鏈接。數據庫

       2.代碼 apache

       // 查詢全部用戶設計模式

 

[java]  view plain  copy
 
 print?
  1. Public void FindAllUsers(){  
  2.        //一、裝載sqlserver驅動對象  
  3.        DriverManager.registerDriver(new SQLServerDriver());               
  4.        //二、經過JDBC創建數據庫鏈接  
  5.        Connection con =DriverManager.getConnection("jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");              
  6.        //三、建立狀態  
  7.        Statement state =con.createStatement();             
  8.        //四、查詢數據庫並返回結果  
  9.        ResultSet result =state.executeQuery("select * from users");             
  10.        //五、輸出查詢結果  
  11.        while(result.next()){  
  12.               System.out.println(result.getString("email"));  
  13.        }              
  14.        //六、斷開數據庫鏈接  
  15.        result.close();  
  16.        state.close();  
  17.        con.close();  
  18.  }  

 

3.分析api

       程序開發過程當中,存在不少問題:首先,每一次web請求都要創建一次數據庫鏈接。創建鏈接是一個費時的活動,每次都得花費0.05s~1s的時間,並且系統還要分配內存資源。這個時間對於一次或幾回數據庫操做,或許感受不出系統有多大的開銷。但是對於如今的web應用,尤爲是大型電子商務網站,同時有幾百人甚至幾千人在線是很正常的事。在這種狀況下,頻繁的進行數據庫鏈接操做勢必佔用不少的系統資源,網站的響應速度一定降低,嚴重的甚至會形成服務器的崩潰。不是危言聳聽,這就是制約某些電子商務網站發展的技術瓶頸問題。其次,對於每一次數據庫鏈接,使用完後都得斷開。不然,若是程序出現異常而未能關閉,將會致使數據庫系統中的內存泄漏,最終將不得不重啓數據庫。還有,這種開發不能控制被建立的鏈接對象數,系統資源會被毫無顧及的分配出去,如鏈接過多,也可能致使內存泄漏,服務器崩潰。緩存

       上述的用戶查詢案例,若是同時有1000人訪問,就會不斷的有數據庫鏈接、斷開操做:

 

 

       經過上面的分析,咱們能夠看出來,「數據庫鏈接」是一種稀缺的資源,爲了保障網站的正常使用,應該對其進行妥善管理。其實咱們查詢完數據庫後,若是不關閉鏈接,而是暫時存放起來,當別人使用時,把這個鏈接給他們使用。就避免了一次創建數據庫鏈接和斷開的操做時間消耗。原理以下:

二. 技術演進出來的數據庫鏈接池

       由上面的分析能夠看出,問題的根源就在於對數據庫鏈接資源的低效管理。咱們知道,對於共享資源,有一個很著名的設計模式:資源池(resource pool)。該模式正是爲了解決資源的頻繁分配﹑釋放所形成的問題。爲解決上述問題,能夠採用數據庫鏈接池技術。數據庫鏈接池的基本思想就是爲數據庫鏈接創建一個「緩衝池」。預先在緩衝池中放入必定數量的鏈接,當須要創建數據庫鏈接時,只需從「緩衝池」中取出一個,使用完畢以後再放回去。咱們能夠經過設定鏈接池最大鏈接數來防止系統無盡的與數據庫鏈接。更爲重要的是咱們能夠經過鏈接池的管理機制監視數據庫的鏈接的數量﹑使用狀況,爲系統開發﹑測試及性能調整提供依據。

       咱們本身嘗試開發一個鏈接池,來爲上面的查詢業務提供數據庫鏈接服務:

       ①   編寫class 實現DataSource 接口

       ②   在class構造器一次性建立10個鏈接,將鏈接保存LinkedList中

       ③   實現getConnection  從 LinkedList中返回一個鏈接

       ④   提供將鏈接放回鏈接池中方法

 

       一、鏈接池代碼       

[java]  view plain  copy
 
 print?
  1. public class MyDataSource implements DataSource {  
  2.           //鏈表 --- 實現棧結構  
  3.           privateLinkedList<Connection> dataSources = new LinkedList<Connection>();  
  4.   
  5.           //初始化鏈接數量  
  6.           publicMyDataSource() {  
  7.                  //一次性建立10個鏈接  
  8.                  for(int i = 0; i < 10; i++) {  
  9.                         try {  
  10.                            //一、裝載sqlserver驅動對象  
  11.                            DriverManager.registerDriver(new SQLServerDriver());  
  12.                            //二、經過JDBC創建數據庫鏈接  
  13.                            Connection con =DriverManager.getConnection(  
  14.                               "jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");  
  15.                            //三、將鏈接加入鏈接池中  
  16.                            dataSources.add(con);  
  17.                         } catch (Exception e) {  
  18.                            e.printStackTrace();  
  19.                         }  
  20.                  }  
  21.           }  
  22.   
  23.           @Override  
  24.           publicConnection getConnection() throws SQLException {  
  25.                  //取出鏈接池中一個鏈接  
  26.                  finalConnection conn = dataSources.removeFirst(); // 刪除第一個鏈接返回  
  27.                  returnconn;  
  28.           }  
  29.   
  30.           //將鏈接放回鏈接池  
  31.           publicvoid releaseConnection(Connection conn) {  
  32.                  dataSources.add(conn);  
  33.                  }  
  34.    }  

 

       二、使用鏈接池重構咱們的用戶查詢函數       

[java]  view plain  copy
 
 print?
  1. //查詢全部用戶  
  2. Public void FindAllUsers(){  
  3.        //一、使用鏈接池創建數據庫鏈接  
  4.        MyDataSource dataSource = new MyDataSource();  
  5.        Connection conn =dataSource.getConnection();          
  6.        //二、建立狀態  
  7.        Statement state =con.createStatement();             
  8.        //三、查詢數據庫並返回結果  
  9.        ResultSet result =state.executeQuery("select * from users");             
  10.        //四、輸出查詢結果  
  11.        while(result.next()){  
  12.               System.out.println(result.getString("email"));  
  13.        }              
  14.        //五、斷開數據庫鏈接  
  15.        result.close();  
  16.        state.close();  
  17.        //六、歸還數據庫鏈接給鏈接池  
  18.        dataSource.releaseConnection(conn);  
  19.  }  

 

 

       這就是數據庫鏈接池的原理,它大大提供了數據庫鏈接的利用率,減少了內存吞吐的開銷。咱們在開發過程當中,就不須要再關心數據庫鏈接的問題,天然有數據庫鏈接池幫助咱們處理,這回放心了吧。但鏈接池須要考慮的問題不只僅如此,下面咱們就看看還有哪些問題須要考慮。

三.鏈接池還要考慮更多的問題

       一、併發問題

       爲了使鏈接管理服務具備最大的通用性,必須考慮多線程環境,即併發問題。這個問題相對比較好解決,由於java語言自身提供了對併發管理的支持,使用synchronized關鍵字便可確保線程是同步的。使用方法爲直接在類方法前面加上synchronized關鍵字,如:

      publicsynchronized connection getconnection()

 

       二、多數據庫服務器和多用戶

       對於大型的企業級應用,經常須要同時鏈接不一樣的數據庫(如鏈接oracle和sybase)。如何鏈接不一樣的數據庫呢?咱們採用的策略是:設計一個符合單例模式的鏈接池管理類,在鏈接池管理類的惟一實例被建立時讀取一個資源文件,其中資源文件中存放着多個數據庫的url地址等信息。根據資源文件提供的信息,建立多個鏈接池類的實例,每個實例都是一個特定數據庫的鏈接池。鏈接池管理類實例爲每一個鏈接池實例取一個名字,經過不一樣的名字來管理不一樣的鏈接池。

       對於同一個數據庫有多個用戶使用不一樣的名稱和密碼訪問的狀況,也能夠經過資源文件處理,即在資源文件中設置多個具備相同url地址,但具備不一樣用戶名和密碼的數據庫鏈接信息。

 

       三、事務處理

       咱們知道,事務具備原子性,此時要求對數據庫的操做符合「all-all-nothing」原則即對於一組sql語句要麼全作,要麼全不作。

       在java語言中,connection類自己提供了對事務的支持,能夠經過設置connection的autocommit屬性爲false 而後顯式的調用commit或rollback方法來實現。但要高效的進行connection複用,就必須提供相應的事務支持機制。可採用每個事務獨佔一個鏈接來實現,這種方法能夠大大下降事務管理的複雜性。

 

       四、鏈接池的分配與釋放

       鏈接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,能夠提升鏈接的複用度,從而下降創建新鏈接的開銷,同時還能夠加快用戶的訪問速度。

       對於鏈接的管理可以使用空閒池。即把已經建立但還沒有分配出去的鏈接按建立時間存放到一個空閒池中。每當用戶請求一個鏈接時,系統首先檢查空閒池內有沒有空閒鏈接。若是有就把創建時間最長(經過容器的順序存放實現)的那個鏈接分配給他(實際是先作鏈接是否有效的判斷,若是可用就分配給用戶,如不可用就把這個鏈接從空閒池刪掉,從新檢測空閒池是否還有鏈接);若是沒有則檢查當前所開鏈接池是否達到鏈接池所容許的最大鏈接數(maxconn)若是沒有達到,就新建一個鏈接,若是已經達到,就等待必定的時間(timeout)。若是在等待的時間內有鏈接被釋放出來就能夠把這個鏈接分配給等待的用戶,若是等待時間超過預約時間timeout 則返回空值(null)。系統對已經分配出去正在使用的鏈接只作計數,當使用完後再返還給空閒池。對於空閒鏈接的狀態,可開闢專門的線程定時檢測,這樣會花費必定的系統開銷,但能夠保證較快的響應速度。也可採起不開闢專門線程,只是在分配前檢測的方法。

 

       五、鏈接池的配置與維護

       鏈接池中到底應該放置多少鏈接,才能使系統的性能最佳?系統可採起設置最小鏈接數(minconn)和最大鏈接數(maxconn)來控制鏈接池中的鏈接。最小鏈接數是系統啓動時鏈接池所建立的鏈接數。若是建立過多,則系統啓動就慢,但建立後系統的響應速度會很快;若是建立過少,則系統啓動的很快,響應起來卻慢。這樣,能夠在開發時,設置較小的最小鏈接數,開發起來會快,而在系統實際使用時設置較大的,由於這樣對訪問客戶來講速度會快些。最大鏈接數是鏈接池中容許鏈接的最大數目,具體設置多少,要看系統的訪問量,可經過反覆測試,找到最佳點。

       如何確保鏈接池中的最小鏈接數呢?有動態和靜態兩種策略。動態即每隔必定時間就對鏈接池進行檢測,若是發現鏈接數量小於最小鏈接數,則補充相應數量的新鏈接以保證鏈接池的正常運轉。靜態是發現空閒鏈接不夠時再去檢查。

四.實際開發中有成熟的開源鏈接池供咱們使用

       理解了鏈接池的原理就能夠了,沒有必要什麼都從頭寫一遍,那樣會花費不少時間,而且性能及穩定性也不必定知足要求。事實上,已經存在不少流行的性能優良的第三方數據庫鏈接池jar包供咱們使用。如:

       1.Apache commons-dbcp 鏈接池

        下載:http://commons.apache.org/proper/commons-dbcp/

 

       2.c3p0 數據庫鏈接池

        下載:http://sourceforge.net/projects/c3p0/

 

  • c3p0是什麼

  c3p0的出現,是爲了大大提升應用程序和數據庫之間訪問效率的。

  它的特性:

  1. 編碼的簡單易用
  2. 鏈接的複用
  3. 鏈接的管理

  說到c3p0,不得不說一下jdbc自己,c3p0願意就是對數據庫鏈接的管理,那麼原有的概念仍是得清晰:DriverManager、Connection、StateMent、ResultMent。

  jdbc:java database connective這套API,不用多說,是一套用於鏈接各式dbms或鏈接橋接器的api,兩個層級:上層供應用方調用api,下層,定義了各個dbms的spi的api(具體文檔見:這裏)。

  主要要提的是:datasource、DriverManager,想到哪兒寫到哪兒,datasource是更高級一點的api,緣由在於相對對應用來講更透明。

  Connection:同dbms的邏輯連接,相似於session管理概念, SQL statements are executed and results are returned within the context of a connection.

  jdbc的概念就到這裏,平時用得比較多。

  • c3P0的概念

 

  c3p0的bean配置以下:

 1 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
 2         <property name="driverClass" value="${jdbc.driverClassName}" />
 3         <property name="jdbcUrl" value="${jdbc.url}" />
 4         <property name="user" value="${jdbc.username}" />
 5         <property name="password" value="${jdbc.password}" />
 6         <property name="checkoutTimeout" value="30000" />
 7         <property name="maxPoolSize" value="15" />
 8         <property name="idleConnectionTestPeriod" value="180" />
 9         <property name="maxIdleTime" value="180" />
10     </bean>

還有一些配置選項,後續詳細說明。可見c3p0的bean引用使用的是:ComboPooledDataSource,該類結構以下:

 

 

以上類圖都不是很徹底,不過大致能表達出類之間的原理:

一、bean:ComboPooledDataSource的父類:AbstractPoolBackedDataSource有一個poolmanager字段,存儲着對pool管理器

二、獲取ds.getConnection()連接對象時,內部使用getPoolManger()獲取C3p0ConnectionPooledManager(mgr)對象,該manager管理着pool對象:C3P0PooledConnectionPool對象,mgr.getPool().checkoutPooledConnection()

三、自此該connection已經被獲取到了

四、讓咱們看看該connection的真實面目吧:

 ProxyConnection。

五、所以其實原理是:

  從pool裏獲取到的connection,是proxy包裝的connection,而對connection的釋放或者重用,是pool的管理責任:初始化池大小,維護池的大小(expand或shrink),管理unused、expired、checkout、checkin鏈接。

真正底層的鏈接是jdbc本身的鏈接,而c3p0的管理部分,基本上使用的是synchronized關鍵字,使用timerTask定時器工做。

 

配置介紹:

<c3p0-config> 
<default-config> 
<!--當鏈接池中的鏈接耗盡的時候c3p0一次同時獲取的鏈接數。Default: 3 --> 
<property name="acquireIncrement">3</property> 

<!--定義在從數據庫獲取新鏈接失敗後重復嘗試的次數。Default: 30 --> 
<property name="acquireRetryAttempts">30</property> 

<!--兩次鏈接中間隔時間,單位毫秒。Default: 1000 --> 
<property name="acquireRetryDelay">1000</property> 

<!--鏈接關閉時默認將全部未提交的操做回滾。Default: false --> 
<property name="autoCommitOnClose">false</property> 

<!--c3p0將建一張名爲Test的空表,並使用其自帶的查詢語句進行測試。若是定義了這個參數那麼 
屬性preferredTestQuery將被忽略。你不能在這張Test表上進行任何操做,它將只供c3p0測試 
使用。Default: null--> 
<property name="automaticTestTable">Test</property> 

<!--獲取鏈接失敗將會引發全部等待鏈接池來獲取鏈接的線程拋出異常。可是數據源仍有效 
保留,並在下次調用getConnection()的時候繼續嘗試獲取鏈接。若是設爲true,那麼在嘗試 
獲取鏈接失敗後該數據源將申明已斷開並永久關閉。Default: false--> 
<property name="breakAfterAcquireFailure">false</property> 

<!--當鏈接池用完時客戶端調用getConnection()後等待獲取新鏈接的時間,超時後將拋出 
SQLException,如設爲0則無限期等待。單位毫秒。Default: 0 --> 
<property name="checkoutTimeout">100</property> 

<!--經過實現ConnectionTester或QueryConnectionTester的類來測試鏈接。類名需制定全路徑。 
Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester--> 
<property name="connectionTesterClassName"></property> 

<!--指定c3p0 libraries的路徑,若是(一般都是這樣)在本地便可得到那麼無需設置,默認null便可 
Default: null--> 
<property name="factoryClassLocation">null</property> 

<!--Strongly disrecommended. Setting this to true may lead to subtle and bizarre bugs. 
(文檔原文)做者強烈建議不使用的一個屬性--> 
<property name="forceIgnoreUnresolvedTransactions">false</property> 

<!--每60秒檢查全部鏈接池中的空閒鏈接。Default: 0 --> 
<property name="idleConnectionTestPeriod">60</property> 

<!--初始化時獲取三個鏈接,取值應在minPoolSize與maxPoolSize之間。Default: 3 --> 
<property name="initialPoolSize">3</property> 

<!--最大空閒時間,60秒內未使用則鏈接被丟棄。若爲0則永不丟棄。Default: 0 --> 
<property name="maxIdleTime">60</property> 

<!--鏈接池中保留的最大鏈接數。Default: 15 --> 
<property name="maxPoolSize">15</property> 

<!--JDBC的標準參數,用以控制數據源內加載的PreparedStatements數量。但因爲預緩存的statements 
屬於單個connection而不是整個鏈接池。因此設置這個參數須要考慮到多方面的因素。 
若是maxStatements與maxStatementsPerConnection均爲0,則緩存被關閉。Default: 0--> 
<property name="maxStatements">100</property> 

<!--maxStatementsPerConnection定義了鏈接池內單個鏈接所擁有的最大緩存statements數。Default: 0 --> 
<property name="maxStatementsPerConnection"></property> 

<!--c3p0是異步操做的,緩慢的JDBC操做經過幫助進程完成。擴展這些操做能夠有效的提高性能 
經過多線程實現多個操做同時被執行。Default: 3--> 
<property name="numHelperThreads">3</property> 

<!--當用戶調用getConnection()時使root用戶成爲去獲取鏈接的用戶。主要用於鏈接池鏈接非c3p0 
的數據源時。Default: null--> 
<property name="overrideDefaultUser">root</property> 

<!--與overrideDefaultUser參數對應使用的一個參數。Default: null--> 
<property name="overrideDefaultPassword">password</property> 

<!--密碼。Default: null--> 
<property name="password"></property> 

<!--定義全部鏈接測試都執行的測試語句。在使用鏈接測試的狀況下這個一顯著提升測試速度。注意: 
測試的表必須在初始數據源的時候就存在。Default: null--> 
<property name="preferredTestQuery">select id from test where id=1</property> 

<!--用戶修改系統配置參數執行前最多等待300秒。Default: 300 --> 
<property name="propertyCycle">300</property> 

<!--因性能消耗大請只在須要的時候使用它。若是設爲true那麼在每一個connection提交的 
時候都將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable 
等方法來提高鏈接測試的性能。Default: false --> 
<property name="testConnectionOnCheckout">false</property> 

<!--若是設爲true那麼在取得鏈接的同時將校驗鏈接的有效性。Default: false --> 
<property name="testConnectionOnCheckin">true</property> 

<!--用戶名。Default: null--> 
<property name="user">root</property> 

在Hibernate(spring管理)中的配置:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
  <property name="driverClass"><value>oracle.jdbc.driver.OracleDriver</value></property>
  <property name="jdbcUrl"><value>jdbc:oracle:thin:@localhost:1521:Test</value></property>
  <property name="user"><value>Kay</value></property>
  <property name="password"><value>root</value></property>
  <!--鏈接池中保留的最小鏈接數。-->            
    <property name="minPoolSize" value="10" />        
    <!--鏈接池中保留的最大鏈接數。Default: 15 -->         
    <property name="maxPoolSize" value="100" />        
    <!--最大空閒時間,1800秒內未使用則鏈接被丟棄。若爲0則永不丟棄。Default: 0 -->               
    <property name="maxIdleTime" value="1800" />        
    <!--當鏈接池中的鏈接耗盡的時候c3p0一次同時獲取的鏈接數。Default: 3 -->               
    <property name="acquireIncrement" value="3" />         
    <property name="maxStatements" value="1000" />          
    <property name="initialPoolSize" value="10" />          
    <!--每60秒檢查全部鏈接池中的空閒鏈接。Default: 0 -->       
    <property name="idleConnectionTestPeriod" value="60" />          
    <!--定義在從數據庫獲取新鏈接失敗後重復嘗試的次數。Default: 30 -->       
    <property name="acquireRetryAttempts" value="30" />          
    <property name="breakAfterAcquireFailure" value="true" />              
    <property name="testConnectionOnCheckout" value="false" /> 
 </bean>

 

出處:http://blog.csdn.net/shuaihj/article/details/14223015

相關文章
相關標籤/搜索