Java 鏈接池的工做原理

什麼是鏈接?
鏈接,是咱們的編程語言與數據庫交互的一種方式。咱們常常會聽到這麼一句話「數據庫鏈接很昂貴「。

有人接受這種說法,殊不知道它的真正含義。所以,下面我將解釋它到底是什麼。 java

建立鏈接的代碼片斷: mysql

1
2
3
String connUrl ="jdbc:mysql://your.database.domain/yourDBname";
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection (connUrl);

當咱們建立了一個Connection對象,它在內部都執行了什麼: sql

1.「DriverManager」檢查並註冊驅動程序,
2.「com.mysql.jdbc.Driver」就是咱們註冊了的驅動程序,它會在驅動程序類中調用「connect(url…)」方法。
3.com.mysql.jdbc.Driver的connect方法根據咱們請求的「connUrl」,建立一個「Socket鏈接」,鏈接到IP爲「your.database.domain」,默認端口3306的數據庫。
4.建立的Socket鏈接將被用來查詢咱們指定的數據庫,並最終讓程序返回獲得一個結果。

爲何昂貴? 數據庫

如今讓咱們談談爲何說它「昂貴「。 編程

若是建立Socket鏈接花費的時間比實際的執行查詢的操做所花費的時間還要更長。 數組

這就是咱們所說的「數據庫鏈接很昂貴」,由於鏈接資源數是1,它須要每次建立一個Socket鏈接來訪問DB。 tomcat

所以,咱們將使用鏈接池。 服務器

鏈接池初始化時建立必定數量的鏈接,而後從鏈接池中重用鏈接,而不是每次建立一個新的。 併發

怎樣工做? dom

接下來咱們來看看它是如何工做,以及如何管理或重用現有的鏈接。

咱們使用的鏈接池供應者,它的內部有一個鏈接池管理器,當它被初始化:

1.它建立鏈接池的默認大小,好比指定建立5個鏈接對象,並把它存放在「可用」狀態的任何集合或數組中。

例如,代碼片斷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
  String connUrl ="jdbc:mysql://your.database.domain/yourDBname";
  String driver ="com.mysql.jdbc.Driver";
  privateMap<java.sql.Connection, String> connectionPool =null;
  privatevoidinitPool() {
    try{
      connectionPool =newHashMap<java.sql.Connection, String>();
      Class.forName(driver);
      java.sql.Connection con = DriverManager.getConnection(dbUrl);
      for(intpoolInd = poolSize; poolInd <0; poolInd++) {
        connectionPool.put(con,"AVAILABLE");
      }
  }
...

2.當咱們調用connectionProvider.getConnection(),而後它會從集合中獲取一個鏈接,固然狀態也會更改成「不可用」。

例如,代碼片斷:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
  publicjava.sql.Connection getConnection()throwsClassNotFoundException, SQLException
  {
      booleanisConnectionAvailable =true;
      for(Entry<java.sql.Connection, String> entry : connectionPool.entrySet()) {
          synchronized(entry) {
              if(entry.getValue()=="AVAILABLE") {
                  entry.setValue("NOTAVAILABLE");
                  return(java.sql.Connection) entry.getKey();
              }
              isConnectionAvailable =false;
          }
      }
      if(!isConnectionAvailable) {
          Class.forName(driver);
          java.sql.Connection con = DriverManager.getConnection(connUrl);
          connectionPool.put(con,"NOTAVAILABLE");
          returncon;
      }
      returnnull;
  }
  ...

3.當咱們關閉獲得的鏈接,ConnectionProvider是不會真正關閉鏈接。相反,只是將狀態更改成「AVAILABLE」。

例如,代碼片斷:

1
2
3
4
5
6
7
8
9
10
11
12
...
publicvoidcloseConnection(java.sql.Connection connection)throwsClassNotFoundException, SQLException {
    for(Entry<java.sql.Connection, String> entry : connectionPool.entrySet()) {
        synchronized(entry) {
            if(entry.getKey().equals(connection)) {
                //Getting Back the conncetion to Pool
                entry.setValue("AVAILABLE");
            }
        }
    }
}
...

基本上鍊接池的實際工做原理就是這樣,但也有可能使用不一樣的方式。

如今,你可能有一個問題,咱們是否能夠創造咱們本身的鏈接池機制?
 
個人建議是使用已經存在的鏈接池機制,像C3P0DBCP等。

DBCP鏈接池的自我檢測

默認配置的DBCP鏈接池,是不對池中的鏈接作測試的,有時鏈接已斷開了,但DBCP鏈接池不知道,還覺得鏈接是好的呢。

應用從池中取出這樣的鏈接訪問數據庫必定會報錯。這也是好多人不喜歡DBCP的緣由。

問題例一:

MySQL8小時問題,Mysql服務器默認鏈接的「wait_timeout」是8小時,也就是說一個connection空閒超過8個小時,Mysql將自動斷開該 connection。

可是DBCP鏈接池並不知道鏈接已經斷開了,若是程序正巧使用到這個已經斷開的鏈接,程序就會報錯誤。

問題例二:

    之前還使用Sybase數據庫,因爲某種緣由,數據庫死了後重啓、或斷網後恢復。

    等了約10分鐘後,DBCP鏈接池中的鏈接還都是不能使用的(斷開的),訪問數據應用一直報錯,最後只能重啓Tomcat問題才解決 。

解決方案:

    方案一、定時對鏈接作測試,測試失敗就關閉鏈接。

    方案二、控制鏈接的空閒時間達到N分鐘,就關閉鏈接,(而後可再新建鏈接)。

    以上兩個方案使用任意一個就能夠解決以述兩類問題。若是隻使用方案2,建議 N <= 5分鐘。鏈接斷開後最多5分鐘後可恢復。

    也可混合使用兩個方案,建議 N = 30分鐘。

    

    下面就是DBCP鏈接池,同時使用了以上兩個方案的配置配置

validationQuery = "SELECT 1" 驗證鏈接是否可用,使用的SQL語句 testWhileIdle = "true" 指明鏈接是否被空閒鏈接回收器(若是有)進行檢驗.若是檢測失敗,則鏈接將被從池中去除. testOnBorrow = "false" 借出鏈接時不要測試,不然很影響性能 timeBetweenEvictionRunsMillis = "30" 每30秒運行一次空閒鏈接回收器 minEvictableIdleTimeMillis = "1800" 池中的鏈接空閒30分鐘後被回收,默認值就是30分鐘。 numTestsPerEvictionRun="3" 在每次空閒鏈接回收器線程(若是有)運行時檢查的鏈接數量,默認值就是3.

    解釋:

    配置timeBetweenEvictionRunsMillis = "30"後,每30秒運行一次空閒鏈接回收器(獨立線程)。並每次檢查3個鏈接,若是鏈接空閒時間超過30分鐘就銷燬。銷燬鏈接後,鏈接數量就少了,若是小於minIdle數量,就新建鏈接,維護數量很多於minIdle,過行了新老更替。

    testWhileIdle = "true" 表示每30秒,取出3條鏈接,使用validationQuery = "SELECT 1" 中的SQL進行測試 ,測試不成功就銷燬鏈接。銷燬鏈接後,鏈接數量就少了,若是小於minIdle數量,就新建鏈接。

    testOnBorrow = "false" 必定要配置,由於它的默認值是true。false表示每次從鏈接池中取出鏈接時,不須要執行validationQuery = "SELECT 1" 中的SQL進行測試。若配置爲true,對性能有很是大的影響,性能會降低7-10倍。所在必定要配置爲false.

    每30秒,取出numTestsPerEvictionRun條鏈接(本例是3,也是默認值),發出"SELECT 1" SQL語句進行測試 ,測試過的鏈接不算是「被使用」了,還算是空閒的。鏈接空閒30分鐘後會被銷燬。

DBCP鏈接池配置參數注意事項  

--

maxIdle值與maxActive值應配置的接近。

由於,當鏈接數超過maxIdle值後,剛剛使用完的鏈接(剛剛空閒下來)會當即被銷燬。而不是我想要的空閒M秒後再銷燬起一個緩衝做用。這一點DBCP作的可能與你想像的不同。

若maxIdle應與maxActive相差較大,在高負載的系統中會致使頻繁的建立、銷燬鏈接,鏈接數在maxIdle與maxActive間快速頻繁波動,這不是我想要的。

高負載的系統的maxIdle值能夠設置爲與maxActive相同或設置爲-1(-1表示不限制),讓鏈接數量在minIdle與maxIdle間緩衝慢速波動。

 

timeBetweenEvictionRunsMillis建議設置值

initialSize="5",會在tomcat一啓動時,建立5條鏈接,效果很理想。

但同時咱們還配置了minIdle="10",也就是說,最少要保持10條鏈接,那如今只有5條鏈接,哪何時再建立少的5條鏈接呢?

一、等業務壓力上來了, DBCP就會建立新的鏈接。

二、配置timeBetweenEvictionRunsMillis=「時間」,DBCP會啓用獨立的工做線程定時檢查,補上少的5條鏈接。銷燬多餘的鏈接也是同理。

 

鏈接銷燬的邏輯

DBCP的鏈接數會在  0 - minIdle - maxIdle - maxActive  之間變化。變化的邏輯描述以下:

 

默認未配置initialSize(默認值是0)和timeBetweenEvictionRunsMillis參數時,剛啓動tomcat時,鏈接數是0。當應用有一個併發訪問數據庫時DBCP建立一個鏈接。

目前鏈接數量還未達到minIdle,但DBCP也不自動建立新鏈接已使數量達到minIdle數量(沒有一個獨立的工做線程來檢查和建立)。

隨着應用併發訪問數據庫的增多,鏈接數也增多,但都與minIdle值無關,很快minIdle被超越,minIdle值一點用都沒有。

直到鏈接的數量達到maxIdle值,這時的鏈接都是隻增不減的。 再繼續發展,鏈接數再增多並超過maxIdle時,使用完的鏈接(剛剛空閒下來的)會當即關閉,整體鏈接的數量穩定在maxIdle但不會超過maxIdle。

但活動鏈接(在使用中的鏈接)可能數量上瞬間超過maxIdle,但永遠不會超過maxActive。

這時若是應用業務壓力小了,訪問數據庫的併發少了,鏈接數也不會減小(沒有一個獨立的線程來檢查和銷燬),將保持在maxIdle的數量。

 

默認未配置initialSize(默認值是0),但配置了timeBetweenEvictionRunsMillis=「30」(30秒)參數時,剛啓動tomcat時,鏈接數是0。立刻應用有一個併發訪問數據庫時DBCP建立一個鏈接。

目前鏈接數量還未達到minIdle,每30秒DBCP的工做線程檢查鏈接數是否少於minIdle數量,若少於就建立新鏈接直到達到minIdle數量。

隨着應用併發訪問數據庫的增多,鏈接數也增多,直到達到maxIdle值。這期間每30秒DBCP的工做線程檢查鏈接是否空閒了30分鐘,如果就銷 毀。但此時是業務的高峯期,是不會有長達30分鐘的空閒鏈接的,工做線程查了也是白查,但它在工做。到這裏鏈接數量一直是呈現增加的趨勢。

當鏈接數再增多超過maxIdle時,使用完的鏈接(剛剛空閒下來)會當即關閉,整體鏈接的數量穩定在maxIdle。中止了增加的趨勢。但活動鏈接(在使用中的鏈接)可能數量上瞬間超過maxIdle,但永遠不會超過maxActive。

這時若是應用業務壓力小了,訪問數據庫的併發少了,每30秒DBCP的工做線程檢查鏈接(默認每次查3條)是否空閒達到30分鐘(這是默認值),若 鏈接空閒達到30分鐘,就銷燬鏈接。這時鏈接數減小了,呈降低趨勢,將從maxIdle走向minIdle。當小於minIdle值時,則DBCP建立新 鏈接已使數量穩定在minIdle,並進行着新老更替。

 

配置initialSize=「10」時,tomcat一啓動就建立10條鏈接。其它同上。

 

minIdle要與timeBetweenEvictionRunsMillis配合使用纔有用,單獨使用minIdle不會起做用。

相關文章
相關標籤/搜索