在Spring Boot專案中,資料庫連線池已經成為標準配置,但是,我遇到過很多連線池異常導致業務錯誤的事故。 許多有經驗的工程師也可能不小心在這方面遇到問題。
在本文中,我們將仔細研究資料庫連線池,並解釋其實現機制,以便更好地理解和規避潛在風險。
如果沒有連線池,我們可以按如下方式操作資料庫:
應用程式使用資料庫驅動程式建立與資料庫的 TCP 連線;
使用者身份驗證;
身份驗證通過,應用讀取和寫入資料庫;
操作完成後,關閉 TCP 連線。
建立資料庫連線是一項昂貴的操作,如果同時有數百甚至數千人,頻繁的連線操作會占用更多的系統資源,但資料庫支援的連線數量有限,建立大量連線可能會導致資料庫宕機。
當我們有乙個連線池時,應用程式會提前從多個資料庫連線物件開始,然後將連線物件儲存到連線池中。 當客戶請求到達時,將從池中獲取乙個連線物件來為客戶提供服務。 請求完成後,客戶端呼叫 close 方法將連線物件放回池中。
相比之下,連線池的優點是顯而易見的:
1、資源復用:
由於資料庫連線可以復用,避免了頻繁建立,釋放了大量連線帶來的效能開銷,也增加了系統執行環境的穩定性。
2. 提高效能
當業務請求初始化時已建立的資料庫連線時,無需等待連線建立即可立即使用,從而縮短響應時間。
3. 優化資源分配
對於與多個應用共享同一資料庫的系統,可以通過在應用層配置資料庫連線池來限制乙個應用的最大可用資料庫連線數,從而防止乙個應用獨佔所有資料庫資源。
4. 連線管理
在資料庫連線池的實現中,可以按照預占用超時設定強制占用連線,避免在常規資料庫連線操作中可能出現的資源洩漏。
以下 ** 顯示了 JDBC 操作資料庫的過程:
//1.連線連線 = drivermanagergetconnection(jdbcurl, username, password);//2.執行 SQL 查詢字串 sqlquery ="select * from mytable where column1 = ?";preparedstatement preparedstatement = connection.preparestatement(sqlquery);preparedstatement.setstring(1, "somevalue");resultset = preparedstatement.executequery();//3.處理查詢結果,同時 (resultset.next())//4.關閉資源結果集close();preparedstatement.close();connection.close();上述方法經常建立資料庫連線,偶爾在較舊的 JSP 頁面中使用,現在通常用於 JDBC 連線池。
JDBC 連線池具有標準的資料來源介面j**ax.sql.datasource
,它位於 J**A 標準庫中。
public interface datasource extends commondatasource, wrapper常用的JDBC連線池有:
hikaricp
c3p0druid
Druid(阿里巴巴資料庫連線池)是乙個開源的資料庫連線池庫,提供了強大的資料庫連線池管理和監控功能。
1. 配置 druid 資料來源
druiddatasource datasource = new druiddatasource();datasource.seturl("jdbc:mysql://localhost:3306/mydatabase");datasource.setusername("yourusername");datasource.setpassword("yourpassword");datasource.setinitialsize(5);初始連線池大小資料來源setminidle(5);最小空閒連線數資料來源setmaxactive(20);資料來源的最大活動連線數setvalidationquery("select 1 from dual");Heartbeat 的 queryDataSourcesetmaxwait(60000);最長等待時間是 datasourcesettestonborrow(true);驗證連線是否有效2. 使用資料庫連線
connection connection = datasource.getconnection();使用連線執行資料庫操作以執行業務操作 使用連線後關閉連線Connectionclose();3. 關閉資料來源
datasource.close();我們學習習對資料來源的實現,可以從以下五個核心角度進行分析:
初始化。 建立連線。
*連線。 返回連線。
斷開連線。 首先,我們來看一下資料來源的實現建立聯絡介面截圖,初始化即可主動跟被動兩種方式。
主從是指對顯示的 init 方法的呼叫。
叫在初始化方法中,資料來源建立乙個包含三個連線池的陣列。getconnection
方法,則返回的物件是連線介面的封裝類druidconnectionholder
connections:用於儲存可獲取的連線物件。
evictconnections:用於儲存需要丟棄的連線物件。
keepaliveconnections:用於儲存需要保持活動狀態的連線物件。
在初始化階段,您需要將連線池化預熱
也就是說,需要根據配置建立一定數量的連線,並放入池中,這樣應用在需要獲取連線時,就可以直接從池中獲取。
資料來源預熱
分為:同步跟非同步兩種方式,見下圖:
從上圖可以看出,在同步建立連線時,原生 JDBC 會建立連線並將其直接放入 connections 陣列物件中。預熱資料來源後,將啟動兩個任務執行緒:建立連線。 跟斷開連線。非同步建立執行緒需要初始化 createscheduler,但預設未配置。
在本節中,我們將重點介紹習德魯伊資料來源建立連線。
createconnectionthread
本質是一條線在無限迴圈中通過condition
等待,被其他執行緒喚醒,並實現建立資料庫連線的邏輯。
筆者對執行方法進行了適當的簡化,只有滿足條件才會進行資料庫連線:
您必須儲存 ** 程式並等待,然後再建立連線。
防止建立超過最大連線數 maxacitve
建立連線物件physicalconnectioninfo
之後,您需要儲存到connections
陣列,並喚醒到其他執行緒,以便可以從池中獲取連線。
現在,我們已經分解了建立連線的過程,下一步是應用如何獲取它。
druiddatasource#getconnection
方法將被呼叫druiddatasource#getconnectiondirect
方法,實現如下。
核心流程是:
1. 在 for 迴圈中,先呼叫getconnectiondirect
內在,稱為getconnectioninternal
從池中獲取連線物件;
2.獲得連線後,您需要使用以下命令testonborrow
testwhileidle
引數配置決定是否需要檢查連線的有效性
3.最後,如果需要判斷連線是否洩漏,請進行配置removeabandoned
若要關閉長時間不適用的連線,不建議將此功能用於複製環境,僅用於連線洩漏檢測診斷。
讓我們來談談建立聯絡的重點:getconnectioninternal
如何從池中獲取連線。
getconnectioninternal()
有三種方法可以獲取連線:
直接建立連線(預設配置不會執行)。
您需要配置定時執行緒池createscheduler
,當連線池中沒有更多可用連線時,當前借用的連線數未達到允許的最大連線數,並且當前沒有其他執行緒正在建立連線
Polllast 方法:從池中獲取連線並最多等待maxwait需要設定時間maxwait
polllast 方法的核心是在迴圈內部等待,通過條件物件 notempty 的 awaitnanos 方法,如果池中有連線,則取出最後乙個連線,並將最後乙個陣列元素留空。TakeLast 方法:從池中獲取連線並等待,直到獲得連線。
與 polllast 方法不同,方法主體首先是內部的沒有無休止的迴圈,等待條件物件的 notempty await 方法,直到池中存在連線,取出最後乙個連線,並將最後乙個陣列元素留空。
druiddatasource
在連線池中,每個物理連線都打包為:druidconnectionholder
,在將其提供給應用程式執行緒之前,druidconnectionholder
打包成druidpooledconnection
在每次業務操作之後,將執行本機 JDBC 操作以關閉連線,用於在連線池的情況下,就是返回連線,即將連線放回連線池中
下圖說明了這一點druidpooledconnection
之close方法:
在閉合方法中,我們專注於:recycle
*連線。 方法:
我們可以簡單地理解這一點:將連線放在 connections 陣列的 poolingcount 位置,自行遞增,然後通過條件物件 notempty 喚醒等待獲取連線的應用程式。
druiddatasource
連線破壞destroyconnectionthread
執行緒完成:
定時任務的每個間隔(無限迴圈)。timebetweenevictionrunsmillis
做一次,我們將專注於destroytask
之run
方法:
destroytask
之run
方法被呼叫druiddatasource#shrink
方法,根據設定的條件確定哪些連線需要銷毀並保持活動狀態。
核心工藝:1. 遍歷連線池陣列
在內部,這些連線需要被銷毀或保持活動狀態,並新增到相應的容器陣列中。
2. 銷毀場景
閒暇idlemillis
>= 允許的最短空閒時間minevictableidletimemillis
閒暇idlemillis
>= 允許的最大空閒時間maxevictableidletimemillis
3.保持現場活躍
發生致命錯誤 (onfatalerror == true),並且致命錯誤發生在 (lastfatalerrortimemillis
建立連線後。
如果開啟了keepalive機制,並且連線的空閒時間大於或等於keepalive間隔。
4.破壞連線
迴圈訪問逐出連線陣列並逐個銷毀它們。
5. 保持連線處於活動狀態
遍歷陣列 keepaliveconnections 驗證連線,如果驗證失敗,則關閉連線,否則新增鎖並重新加入連線池。
在本節中,我們將解釋如何配置合理的引數以確保資料庫連線有效。
很多同學會遇到乙個問題:“長時間不進行資料庫讀寫操作後,第一次請求資料庫時資料庫會報錯,第二次就正常了。 "
那是因為為了節省資源,資料庫會關閉長時間未讀寫的連線
筆者第一次使用德魯伊時就遇到了這樣的問題,感興趣的同學可以看看筆者的文章:
j**ayong.cn/codelife/runningforcode.html下圖顯示了 druid 資料來源配置示例
下面我們來簡單梳理一下德魯伊的策略,確保連線有效
1. 銷毀連線線程以定時檢測所有連線,關閉空閒時間過多的連線,如果配置了 keepalive 引數,則繼續保持連線保持活動狀態
2. 每次應用程式從資料來源獲取連線時,它都會使用以下條件testonborrow
testwhileidle
該引數檢測連線的有效性。
因此,我們需要重點配置以下引數:
a. Timebetweenevictionrunsmillis 引數:檢查空閒連線是否有效的頻率。
b. testwhileIdle 引數:啟用空閒連線檢測,強烈建議將其設定為 true。
C. mineVictableIdleTimeMillis 引數:連線池中連線的最大空閒時間(毫秒)、minidle >連線數>空閒時間> minevictableidletimemillis。
d. MaxevicTableidleTimeMillis 引數:連線池中連線的最大空閒時間,空閒>時間為 maxevictableidletimemillis,無論連線池中的連線數是否小於最小連線數。
E. testOnBorrow 引數如果設定為true,可以最大程度保證連線的可靠性,但效能會變得很差。
如果資料庫配置連線生存時間很短,那麼就需要適當減少空閒連線檢測間隔,減少最大和最小空閒時間。在本文中,筆者梳理了資料庫連線池的知識點。
1. 連線池的優勢:資源復用、效能提公升、資源配置優化、連線管理;
2. JDBC連線池:實現資料來源介面j**ax.sql.datasource
,位於 J**a 標準庫中;
三、連線池德魯伊的實現原則
核心方法:初始化、建立、獲取、返回和銷毀連線。
儲存容器:連線池陣列、銷毀連線陣列和保持連線陣列。
執行緒模型:獨立建立和銷毀連線線程。
鎖定機制:在建立連線和獲取連線時,會通過兩個條件物件將其鎖定emptynotempty分別控制建立連線線程和獲取連線線程的等待和喚醒。
資料庫連線池和執行緒池都是物件池的概念。 物件池是一種設計模式,用於管理可重用物件,以減少建立和銷毀物件的開銷。
筆者將在以下文章中為大家詳細講解:
如何使用池化框架commons pool
Netty 如何實現簡單的連線池。
作者:勇哥的程式設計遊記。
*:cnblogs.com/makemylife/p/17889584.html