日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

SpringBoot項目中,數據庫連接池已經成為標配,然而,我曾經遇到過不少連接池異常導致業務錯誤的事故。很多經驗豐富的工程師也可能不小心在這方面出現問題。

在這篇文章中,我們將探討數據庫連接池,深入解析其實現機制,以便更好地理解和規避潛在的風險。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

1 為什么需要連接池

假如沒有連接池,我們操作數據庫的流程如下:

  1. 應用程序使用數據庫驅動建立和數據庫的 TCP 連接 ;
  2. 用戶進行身份驗證 ;
  3. 身份驗證通過,應用進行讀寫數據庫操作 ;
  4. 操作結束后,關閉 TCP 連接 。

創建數據庫連接是一個比較昂貴的操作,若同時有幾百人甚至幾千人在線,頻繁地進行連接操作將占用更多的系統資源,但數據庫支持的連接數是有限的,創建大量的連接可能會導致數據庫僵死。

當我們有了連接池,應用程序啟動時就預先建立多個數據庫連接對象,然后將連接對象保存到連接池中。當客戶請求到來時,從池中取出一個連接對象為客戶服務。當請求完成時,客戶程序調用關閉方法,將連接對象放回池中。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

相比之下,連接池的優點顯而易見:

1、資源重用:

因為數據庫連接可以重用,避免了頻繁創建,釋放連接引起的大量性能開銷,同時也增加了系統運行環境的平穩性。

2、提高性能

當業務請求時,因為數據庫連接在初始化時已經被創建,可以立即使用,而不需要等待連接的建立,減少了響應時間。

3、優化資源分配

對于多應用共享同一數據庫的系統而言,可在應用層通過數據庫連接池的配置,實現某一應用最大可用數據庫連接數的限制,避免某一應用獨占所有的數據庫資源。

4、連接管理

數據庫連接池實現中,可根據預先的占用超時設定,強制回收被占用連接,從而避免了常規數據庫連接操作中可能出現的資源泄露。

2 JDBC 連接池

下面的代碼展示了 JDBC 操作數據庫的流程 :

//1. 連接到數據庫
Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
//2. 執行SQL查詢
String sqlQuery = "SELECT * FROM mytable WHERE column1 = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sqlQuery);
preparedStatement.setString(1, "somevalue");
resultSet = preparedStatement.executeQuery();
//3. 處理查詢結果
while (resultSet.next()) {
    int column1Value = resultSet.getInt("column1");
    String column2Value = resultSet.getString("column2");
    System.out.println("Column1: " + column1Value + ", Column2: " + column2Value);
}
//4. 關閉資源
resultSet.close();
preparedStatement.close();
connection.close();

上面的方式會頻繁的創建數據庫連接,在比較久遠的 JSP 頁面中會偶爾使用,現在普遍使用 JDBC 連接池。

JDBC 連接池有一個標準的數據源接口JAVAx.sql.DataSource,這個類位于 Java 標準庫中。

public interface DataSource  extends CommonDataSource, WrApper {

  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password) throws SQLException;
}

常用的 JDBC 連接池有:

  • HikariCP
  • C3P0
  • Druid

Druid(阿里巴巴數據庫連接池)是一個開源的數據庫連接池庫,它提供了強大的數據庫連接池管理和監控功能。

1)配置Druid數據源

DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:MySQL://localhost:3306/mydatabase");
dataSource.setUsername("yourusername");
dataSource.setPassword("yourpassword");
dataSource.setInitialSize(5); // 初始連接池大小
dataSource.setMinIdle(5); // 最小空閑連接數
dataSource.setMaxActive(20); // 最大活動連接數
dataSource.setValidationQuery("select 1 from dual");  // 心跳的 Query
dataSource.setMaxWAIt(60000); // 最大等待時間
dataSource.setTestOnBorrow(true); // 驗證連接是否有效

2)使用數據庫連接

Connection connection = dataSource.getConnection();
//使用連接執行數據庫操作
// TODO 業務操作
// 使用后關閉連接連接
connection.close();

3)關閉數據源

dataSource.close();

3 連接池 Druid 實現原理

我們學習數據源的實現,可以從如下五個核心角度分析:

  • 初始化
  • 創建連接
  • 回收連接
  • 歸還連接
  • 銷毀連接

3.1 初始化

首先我們查看數據源實現「獲取連接」的接口,初始化可以主動和被動兩種方式。

主從是指顯示的調用 init 方法,而被動是指獲取連接時完成初始化。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

調用getConnection方法時,返回的對象是連接接口的封裝類 DruidConnectionHolder 。

在初始化方法內,數據源創建三個連接池數組 ,他們分別是:

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

  • connections:用于存放能獲取的連接對象。
  • evictConnections:用于存放需要丟棄的連接對象。
  • keepAliveConnections:用于存放需要保活的連接對象。

初始化階段,需要進行連接池的「預熱」:也就是需要按照配置首先創建一定數量的連接,并放入到池子里,這樣應用在需要獲取連接的候,可以直接從池子里獲取。

數據源「預熱」分為同步和異步兩種方式  ,見下圖:

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

從上圖,我們可以看到同步創建連接時,是原生 JDBC 創建連接后,直接放入到 connections 數組對象里。

異步創建線程需要初始化 createScheduler , 但默認并沒有配置。

數據源預熱之后,啟動了兩個任務線程:創建連接線程和銷毀連接線程。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

3.2 創建連接

這一節,我們重點學習 Druid 數據源如何創建連接。

CreateConnectionThread 本質是一個單線程在死循環中通過 condition 等待,被其他線程喚醒 ,并實現創建數據庫連接邏輯。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

筆者將 run 方法做了適當簡化,當滿足了條件之后,才創建數據庫連接 :

  • 必須存在線程等待,才創建連接 。
  • 防止創建超過最大連接數 maxAcitve 。

創建完連接對象 PhysicalConnectionInfo 之后,需要保存到 Connections 數組里,并喚醒到其他的線程,這樣就可以從池子里獲取連接。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

3.3 獲取連接

我們詳細解析了創建連接的過程,接下來就是應用如何獲取連接的過程。

DruidDataSource#getConnection 方法會調用到 DruidDataSource#getConnectionDirect 方法來獲取連接,實現如下所示。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

核心流程是

1)在 for 循環內,首先調用 getConnectionDirect內,調用getConnectionInternal 從池子里獲取連接對象;

2)獲取連接后,需要根據 testOnBorrow 、testWhileIdle 參數配置判斷是否需要檢測連接的有效性;

3)最后假如需要判斷連接是否有泄露,則配置 removeAbandoned 來關閉長時間不適用的連接,該功能不建議再生產環境中使用,僅用于連接泄露檢測診斷。

接下來進入獲取連接的重點:getConnectionInternal 方法如何從池子里獲取連接。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

getConnectionInternal()方法中拿到連接的方式有三種:

  • 直接創建連接(默認配置不會執行)需要配置定時線程池 createScheduler,當連接池已經沒有可用連接,且當前借出的連接數未達到允許的最大連接數,且當前沒有其它線程在創建連接 ;
  • pollLast 方法:從池中拿連接,并最多等待 maxWait 的時間,需要設置了maxWait;

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

pollLast 方法的核心是:死循環內部,通過 Condition 對象 notEmpty 的 awaitNanos 方法執行等待,若池子中有連接,將最后一個連接取出,并將最后一個數組元素置為空。

takeLast 方法:從池中拿連接,并一直等待直到拿到連接。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!

和 pollLast 方法不同,首先方法體內部并沒有死循環,通過 Condition 對象 notEmpty 的 await 方法等待,直到池子中有連接,將最后一個連接取出,并將最后一個數組元素置為空。

3.4 歸還連接

DruidDataSource 連接池中,每一個物理連接都會被包裝成DruidConnectionHolder,在提供給應用線程前,還會將 DruidConnectionHolder 包裝成 DruidPooledConnection。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

原生的 JDBC 操作, 每次執行完業務操作之后,會執行關閉連接,對于連接池來講,就是歸還連接,也就是將連接放回連接池。

下圖展示了 DruidPooledConnection 的 close 方法 :

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

在關閉方法中,我們重點關注 recycle 回收連接方法。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

我們可以簡單的理解:將連接放到 connections 數組的 poolingCount 位置,并將其自增,然后通過 Condition 對象 notEmpty 喚醒等待獲取連接的一個應用程序。

3.5 銷毀連接

DruidDataSource 連接的銷毀 DestroyConnectionThread 線程完成 :

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

從定時任務(死循環)每隔 timeBetweenEvictionRunsMillis 執行一次,我們重點關注destroyTask的run方法。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

destroyTask的run方法 會調用DruidDataSource#shrink方法來根據設定的條件來判斷出需要銷毀和保活的連接。

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

核心流程:

1)遍歷連接池數組 connections:

內部分別判斷這些連接是需要銷毀還是需要保活 ,并分別加入到對應的容器數組里。

2)銷毀場景:

  • 空閑時間idleMillis  >= 允許的最小空閑時間 minEvictableIdleTimeMillis
  • 空閑時間idleMillis  >= 允許的最大空閑時間 maxEvictableIdleTimeMillis

3)保活場景:

  • 發生了致命錯誤(onFatalError == true)且致命錯誤發生時間(lastFatalErrorTimeMillis)在連接建立時間之后
  • 如果開啟了保活機制,且連接空閑時間大于等于了保活間隔時間

4)銷毀連接:

遍歷數組 evictConnections 所有的連接,并逐一銷毀 。

5)保活連接:

遍歷數組 keepAliveConnections 所有的連接,對連接進行驗證 ,驗證失敗,則關閉連接,否則加鎖,重新加入到連接池中。

4 保證連接有效

本節,我們講解如何合理的配置參數保證數據庫連接有效。

很多同學都會遇到一個問題:“長時間不進行數據庫讀寫操作之后,第一次請求數據庫,數據庫會報錯,但第二次就正常了。"

那是因為數據庫為了節省資源,會關閉掉長期沒有讀寫的連接。

筆者第一次使用 Druid 時就遇到過這樣的問題,有興趣的同學可以看看筆者這篇文章:

https://www.javayong.cn/codelife/runningforcode.html

下圖展示了 Druid 數據源配置樣例:

再有人問你數據庫連接池 Druid 的原理,這篇文章甩給他!圖片

我們簡單梳理下 Druid 的保證連接有效有哪些策略:

1、銷毀連接線程定時檢測所有的連接,關閉空閑時間過大的連接 ,假如配置了保活參數,那么會繼續維護待保活的連接;

2、應用每次從數據源中獲取連接時候,會根據testOnBorrow、testWhileIdle參數檢測連接的有效性。

因此,我們需要重點配置如下的參數:

A、timeBetweenEvictionRunsMillis 參數:間隔多久檢測一次空閑連接是否有效。

B、testWhileIdle 參數:啟空閑連接的檢測,強烈建議設置為 true 。

C、minEvictableIdleTimeMillis 參數:連接池中連接最大空閑時間(毫秒),連接數 > minIdle && 空閑時間 > minEvictableIdleTimeMillis 。

D、maxEvictableIdleTimeMillis 參數:連接池中連接最大空閑時間,空閑時間 > maxEvictableIdleTimeMillis,不管連接池中的連接數是否小于最小連接數 。

E、testOnBorrow 參數:開啟連接的檢測,獲取連接時檢測是否有效,假如設置為 true ,可以最大程度的保證連接的可靠性,但性能會變很差 。

筆者建議在配置這些參數時,和 DBA、架構師做好提前溝通,每個公司的數據庫配置策略并不相同,假如數據庫配置連接存活時間很短,那么就需要適當減少空閑連接檢測間隔,并調低最大和最小空閑時間。

5 總結

這篇文章,筆者整理了數據庫連接池的知識點。

1)連接池的優點:資源重用、提高性能、優化資源分配、連接管理;

2)JDBC 連接池:實現數據源接口javax.sql.DataSource,這個類位于 Java 標準庫;

3)連接池 Druid 實現原理:

  • 核心方法:初始化、創建連接、獲取連接、歸還連接、銷毀連接。
  • 存儲容器:連接池數組、銷毀連接數組、保活連接數組。
  • 線程模型:獨立的創建連接線程和銷毀連接線程。
  • 鎖機制:在創建連接、獲取連接時,都會加鎖,通過兩個 Condition 對象 empty 、notEmpty 分別控制創建連接線程和獲取連接線程的等待和喚醒。

4)連接池保活策略

配置連接池參數時,和 DBA、架構師做好提前溝通,每個公司的數據庫配置策略并不相同,假如數據庫配置連接存活時間很短,那么就需要適當減少空閑連接檢測間隔,并調低最大和最小空閑時間。

最后,數據庫連接池、線程池都是對象池的思想。對象池是一種設計模式,用于管理可重復使用的對象,以減少對象的創建和銷毀開銷。

筆者會在接下來的文章里為大家詳解,敬請期待:

  • 如何使用池化框架 Commons Pool ;

參考文章:

https://segmentfault.com/a/1190000043208041

https://blog.csdn.net/weixin_43790613/article/details/133940617

https://blog.csdn.net/yaomingyang/article/details/123145662

分享到:
標簽:Druid
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定