資料庫連線池原理

vipshop_fin_dev發表於2018-08-27

一、JDBC介紹

1.jdbc的定義
JDBC(Java DataBase Connectivity,java資料庫連線)是一種用於執行SQL語句的Java API,可以為多種關聯式資料庫提供統一訪問,它由一組用Java語言編寫的類和介面組成。JDBC提供了一種基準,據此可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。
2.jdbc的用途
簡單地說,JDBC 可做三件事:與資料庫建立連線、傳送 運算元據庫的語句並處理結果。
這裡寫圖片描述
下列程式碼段給出了以上三步的基本示例:

Connection con = DriverManager.getConnection("jdbc:odbc:wombat","login",
"password");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next()) {
int x = rs.getInt("a");
String s = rs.getString("b");
float f = rs.getFloat("c");
}

這裡寫圖片描述
3.操作jdbc的問題
jdbc每一次建立連線都非常耗時,對於一個複雜的資料庫應用,頻繁的建立、關閉連線,會極大的減低系統的效能,因為對於連線的使用成了系統效能的瓶頸。另外,每次由執行緒建立連線、關閉連線,總連線數的控制等等都是非常繁瑣且容易出錯,對資料庫連線的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的效能指標。針對這些問題,需要一個統一管理的框架,防止這些複雜性的擴散。而資料庫連線池就是解決這些問題最有效的方式。

二、資料庫連線池基本原理

1.資料庫連線池基本概念
資料庫連線池負責分配、管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是再重新建立一個;釋放空閒時間超過最大空閒時間的資料庫連線來避免因為沒有釋放資料庫連線而引起的資料庫連線遺漏。這項技術能明顯提高對資料庫操作的效能。
2.連線池的優勢
資料庫連線池技術帶來的優勢:
2.1. 資源重用
由於資料庫連線得到重用,避免了頻繁建立、釋放連線引起的大量效能開銷。在減少系統消耗的基礎上,另一方面也增進了系統執行環境的平穩性(減少記憶體碎片以及資料庫臨時程式/執行緒的數量)。
2.2. 更快的系統響應速度
資料庫連線池在初始化過程中,往往已經建立了若干資料庫連線置於池中備用。此時連線的初始化工作均已完成。對於業務請求處理而言,直接利用現有可用連線,避免了資料庫連線初始化和釋放過程的時間開銷,從而縮減了系統整體響應時間。
2.3. 新的資源分配手段
對於多應用共享同一資料庫的系統而言,可在應用層通過資料庫連線的配置,實現資料庫連線池技術。某一應用最大可用資料庫連線數的限制,避免某一應用獨佔所有資料庫資源。
2.4. 統一的連線管理,避免資料庫連線洩漏
在較為完備的資料庫連線池實現中,可根據預先的連線佔用超時設定,強制收回被佔用連線。從而避免了常規資料庫連線操作中可能出現的資源洩漏。
3.資料庫連線池的工作原理
這裡寫圖片描述
關鍵的配置引數功能:
這裡寫圖片描述

三、實現一個簡單的連線池

基於上面的理解,我們實現一個簡單的連線池。

/*
1.執行緒安全
2.有空閒連線的數量
3.有正在使用的連線數量
*/
6
public class PoolConfig{

  /*
  資料庫jdbc屬性

  */

  private String driverName;//資料庫的驅動類

  private String url;//資料庫的連線地址

  private String userName;//資料庫使用者名稱

  private String password;//資料庫密碼


  /*

  連線池配置

  */

  private int minConn = 1;//空閒集合中最少連線數

  private int maxConn = 5;//空閒集合最多的連線數

  private int initConn = 5;//初始連線數

  private int maxActiveConn = 10;//整個連線池(資料庫)允許的最大連線數

  private int waitTime = 1000;//單位毫秒,連線數不夠時,執行緒等待的時間

  private boolean isCheck = false;//資料庫連線池是否啟用自檢機制(間隔一段時間檢測連線池狀態)

  private long checkPeriod = 1000*30;//自檢週期



  //以下省略getter、setter方法...

}
db.properties配置檔案

jdbc.driverName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncode=true&characterEncoding=utf-8
jdbc.userName=root
jdbc.password=root
DBUtil.java資料庫工具包(資料庫開發者呼叫)

public class DBUtil{

  /* 靜態資料庫配置實體物件,程式執行時載入進記憶體 */

  private static PoolConfig config = new PoolConfig(); 



  static{//初始化載入配置檔案

      Properties prop = new Properties();

      try{

          prop.load(DBUtil.class.getClassLoader().

                     getResourceAsStream

                    ("com/mypath/db/db.properties"));

          //獲取配置檔案資訊傳入config連線池配置物件

          config.setDriverName(prop.getProperty("jdbc.driverName"));

          config.setUrl(prop.getProperty("jdbc.url"));

          config.setUserName(prop.getProperty("jdbc.userName"));

          config.setPassword(prop.getProperty("jdbc.password"));

          //反射載入這個驅動(使用的是JDBC的驅動載入方式)

          Class.forName(config.getDriverName());

      }catch(IOException e){

          e.printStackTrace();

      }

  }



  private static ConnectionPool connPool = new ConnectionPool(config);



  public static connection gerConnection(){

      return connPool.getConnection();

  }



  public static connection gerCurrentConnection(){

      return connPool.getCurrentConnection();

  }



  public static void closeConnection(Connection conn){

      connPool.releaseConnection(conn);

  }

}
ConnectionPool.java資料庫連線池物件(根據配置建立對應連線池)


public class ConnectionPool{

 private PoolConfig config;//連線池的配置物件

 private int count;//記錄連線池的連線數

 private boolean isActive;//連線池是否被啟用

 //空閒連線集合

 private Vector<Connection> freeConn = new Vector<Connection>();

 //正在使用的連線集合

 private Vector<Connection> userConn = new Vector<Connection>();

 //同一個執行緒無論請求多少次都使用同一個連線(使用ThreadLocal確保)

 //每一個執行緒都私有一個連線

 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

 /*

 初始化連線池配置

 */

 public ConnectionPool(PoolConfig config){

     this.config = config;

 }



 /*

 資料庫連線池初始化

 */

 public void init(){

     for(int i=0;i<config.getInitConn();i++){//建立初始連線

         //獲取連線物件

         Connection conn;

         try{

             conn = getNewConnection();

             freeConn.add(conn);

             count++;

         }catche(SQLException e){

             e.printStackTrace();

         }

         isActive = true;//連線池啟用

     }

 }



 /*

 獲取新資料庫連線

 */

 private synchronized Connection getNewConnection() throws SQLException{

     Connection conn = null;

     conn = DriverManager.getConnection(config.getUrl(),

                                       config.getUserName(),

                                       config.getPassword());

     return conn;

 }



 /*

 從連線池獲取連線

 */

 public synchronized Conenction getConnection(){

     Connection conn = null;

     //當前連線總數小於配置的最大連線數才去獲取

     if(count<config.getMaxActiveConn()){

         //空閒集合中有連線數

         if(freeConn.size()>0){

             conn = freeConn.get(0);//從空閒集合中取出

             freeConn.remove(0);//移除該連線

         }else{

             conn = getNewConnection();//拿到新連線

             count++;

         }

         if(isEnable(conn)){

             useConn.add(conn);//新增到已經使用的連線

         }else{

             count--;

             conn = getConnection();//遞迴呼叫到可用的連線

         }

     }else{//當達到最大連線數,只能阻塞等待

         wait(config.getWaitTime());//執行緒睡眠了一段時間

         conn = getConnection();//遞迴呼叫

     }catch(Exception e){

         e.printStackTrace();

     }

     //將獲取的conn設定到本地變數ThreadLocal

     threadLocal.set(conn);

     return conn;

 }



 /*

 把用完的連線放回連線池集合Vector中

 */

 public synchronized void releaseConnection(Connection conn){

     if(isEnable(conn)){

         if(freeConn.size()<config.getMaxConn()){//空閒連線數沒有達到最大

             freeConn.add(conn);//放回集合

         }else{

             conn.close();

         }

     }

     useConn.remove(conn);

     count--;

     threadLocal.remove();

     notifyAll();//放回連線池後說明有連線可用,喚醒阻塞的執行緒獲取連線

 }



 /*

 獲取當前執行緒的本地變數連線

 */

 public Connection getCurrentConnection(){

     return threadLocal.get();    

 }



 /*

 判斷該連線是否可用

 */

 private boolean isEnable(Connection conn){

     if(conn == null){

         return false;

     }

     if(conn.isClosed()){

         return false;

     }

     return true;

 }

}

四、市面上常用的連線池

在Java中開源的資料庫連線池有以下幾種 :
1、C3P0:是一個開放原始碼的JDBC連線池,它在lib目錄中與Hibernate [2] 一起釋出,包括了實現jdbc3和jdbc2擴充套件規範說明的Connection 和Statement 池的DataSources 物件。
2、Proxool:是一個Java SQL Driver驅動程式,提供了對選擇的其它型別的驅動程式的連線池封裝。可以非常簡單的移植到現存的程式碼中,完全可配置,快速、成熟、健壯。可以透明地為現存的JDBC驅動程式增加連線池功能。
3、Jakarta DBCP:DBCP是一個依賴Jakartacommons-pool物件池機制的資料庫連線池。DBCP可以直接的在應用程式中使用。
4、DDConnectionBroker:是一個簡單、輕量級的資料庫連線池。
5、DBPool:是一個高效、易配置的資料庫連線池。它除了支援連線池應有的功能之外,還包括了一個物件池,使使用者能夠開發一個滿足自己需求的資料庫連線池。
6、XAPool:是一個XA資料庫連線池。它實現了javax.sql.XADataSource並提供了連線池工具。
7、Primrose:是一個Java開發的資料庫連線池。當前支援的容器包括Tomcat4&5、Resin3與JBoss3。它同樣也有一個獨立的版本,可以在應用程式中使用而不必執行在容器中。Primrose通過一個WEB介面來控制SQL處理的追蹤、配置,以及動態池管理。在重負荷的情況下可進行連線請求佇列處理。
8、SmartPool:是一個連線池元件,它模仿應用伺服器物件池的特性。SmartPool能夠解決一些臨界問題如連線洩漏(connection leaks)、連線阻塞、開啟的JDBC物件(如Statements、PreparedStatements)等。SmartPool的特性包括:
支援多個pool
自動關閉相關聯的JDBC物件
在所設定time-outs之後察覺連線洩漏
追蹤連線使用情況
強制啟用最近最少用到的連線
把SmartPool“包裝”成現存的一個pool
9、MiniConnectionPoolManager:是一個輕量級JDBC資料庫連線池。它只需要Java1.5(或更高)並且沒有依賴第三方包。
10、BoneCP:是一個快速、開源的資料庫連線池。幫使用者管理資料連線,讓應用程式能更快速地訪問資料庫。比C3P0/DBCP連線池速度快25倍。
11、Druid:Druid不僅是一個資料庫連線池,還包含一個ProxyDriver、一系列內建的JDBC元件庫、一個SQL Parser。
支援所有JDBC相容的資料庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。
Druid針對Oracle和MySql做了特別優化,比如:
Oracle的PS Cache記憶體佔用優化
MySql的ping檢測優化
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支援,這是一個手寫的高效能SQL Parser,支援Visitor模式,使得分析SQL的抽象語法樹很方便。
簡單SQL語句用時10微秒以內,複雜SQL用時30微秒。
通過Druid提供的SQL Parser可以在JDBC層攔截SQL做相應處理,比如說分庫分表、審計等。Druid防禦SQL隱碼攻擊的WallFilter,就是通過Druid的SQL Parser分析語義實現的

相關文章