高并發(fā)下連接池:性能飛升的魔法秘籍
在當(dāng)今數(shù)字化浪潮中,高并發(fā)場景無處不在,從電商平臺促銷時(shí)的海量訂單處理,到在線直播的實(shí)時(shí)互動,再到社交平臺的消息推送。這些場景下,系統(tǒng)如同置身于洶涌的流量洪峰之中,面臨著前所未有的壓力。而在這其中,數(shù)據(jù)庫連接操作就像是系統(tǒng)的 “交通樞紐”,每一次連接的創(chuàng)建、使用與釋放,都關(guān)乎著系統(tǒng)的整體性能。
傳統(tǒng)模式下頻繁創(chuàng)建和銷毀數(shù)據(jù)庫連接,就如同在交通高峰期不斷開辟和關(guān)閉道路,不僅效率低下,還極易引發(fā) “交通堵塞”,導(dǎo)致系統(tǒng)響應(yīng)遲緩甚至崩潰。這時(shí)連接池宛如一位智能交通指揮官,通過復(fù)用連接資源,極大提升了系統(tǒng)處理高并發(fā)請求的效率。今天,就讓我們一同深入連接池的設(shè)計(jì)與優(yōu)化世界,探尋讓系統(tǒng)性能飛升的魔法秘籍 。
一、為什么要使用連接池?
數(shù)據(jù)庫連接是一種關(guān)鍵的有限的昂貴的資源,這一點(diǎn)在多用戶的網(wǎng)頁應(yīng)用程序中體現(xiàn)得尤為突出。一個(gè)數(shù)據(jù)庫連接對象均對應(yīng)一個(gè)物理數(shù)據(jù)庫連接,每次操作都打開一個(gè)物理連接,使用完都關(guān)閉連接,這樣造成系統(tǒng)的 性能低下。數(shù)據(jù)庫連接池的解決方案是在應(yīng)用程序啟動時(shí)建立足夠的數(shù)據(jù)庫連接,并將這些連接組成一個(gè)連接池(簡單說:在一個(gè)“池”里放了好多半成品的數(shù)據(jù)庫連接對象),由應(yīng)用程序動態(tài)地對池中的連接進(jìn)行申請、使用和釋放。對于多于連接池中連接數(shù)的并發(fā)請求,應(yīng)該在請求隊(duì)列中排隊(duì)等待。
傳統(tǒng)數(shù)據(jù)庫連接池
并且應(yīng)用程序可以根據(jù)池中連接的使用率,動態(tài)增加或減少池中的連接數(shù)。連接池技術(shù)盡可能多地重用了消耗內(nèi)存地資源,大大節(jié)省了內(nèi)存,提高了服務(wù)器地服務(wù)效率,能夠支持更多的客戶服務(wù)。通過使用連接池,將大大提高程序運(yùn)行效率,同時(shí),我們可以通過其自身的管理機(jī)制來監(jiān)視數(shù)據(jù)庫連接的數(shù)量、使用情況等。
由于數(shù)據(jù)庫連接的建立和關(guān)閉都會消耗系統(tǒng)資源,而且一個(gè)數(shù)據(jù)庫服務(wù)器能夠同時(shí)創(chuàng)建的連接數(shù)量也是有限的。傳統(tǒng)數(shù)據(jù)庫訪問的方式是一個(gè)數(shù)據(jù)訪問對應(yīng)一個(gè)物理連接,每次操作數(shù)據(jù)庫都需要打開和關(guān)閉物理連接,系統(tǒng)性能嚴(yán)重受損。特別是對于復(fù)雜的數(shù)據(jù)庫應(yīng)用,頻繁建立關(guān)閉連接會極大地降低系統(tǒng)的性能,因此對于連接的使用成了系統(tǒng)性能的瓶頸。通過建立一個(gè)數(shù)據(jù)庫連接池以及一套連接使用管理策略,使一個(gè)數(shù)據(jù)庫連接可以得到高效且安全的復(fù)用,避免了數(shù)據(jù)庫連接頻繁建立和關(guān)閉帶來的開銷。
在普通的數(shù)據(jù)庫訪問程序的過程中,客戶端程序得到的連接是物理連接,調(diào)用連接對象的close()方法將關(guān)閉連接。而采用連接池技術(shù),客戶端程序得到的連接對象是連接池中物理連接的一個(gè)句柄,調(diào)用連接對象的close()方法,物理連接并沒有關(guān)閉。數(shù)據(jù)源的實(shí)現(xiàn)只是刪除了客戶端程序中的連接對象和池中的對象之間的聯(lián)系。
針對資源共享的設(shè)計(jì)模式“資源池”為了解決資源頻繁分配和釋放所造成的問題,將“資源池”模式應(yīng)用到數(shù)據(jù)庫連接管理領(lǐng)域,也就是建立一個(gè)數(shù)據(jù)庫連接池,提供一套高效的連接分配和使用策略,最終實(shí)現(xiàn)連接的高效和安全的復(fù)用。數(shù)據(jù)庫連接池的基本原理是在內(nèi)部對象池中維護(hù)一定數(shù)量的數(shù)據(jù)庫連接,并對外暴露數(shù)據(jù)庫連接獲取和返回的方法。
1. 數(shù)據(jù)庫連接池的基本思想是什么樣的呢?
連接池的基本思想是在系統(tǒng)初始化時(shí),將數(shù)據(jù)庫連接作為對象存儲到內(nèi)存中,當(dāng)用戶需要訪問數(shù)據(jù)庫時(shí),并非建立一個(gè)新的連接,而是從連接池中取出一個(gè)已經(jīng)建立的空閑連接對象。當(dāng)使用完畢后,用戶也并非將連接關(guān)閉,而是將連接放回連接池中供下一個(gè)請求訪問使用。連
接池中的連接的建立和斷開都時(shí)由連接池自身來管理,同時(shí)可以通過設(shè)置連接池的參數(shù)來控制連接池中的初始連接數(shù)、連接的上下限數(shù)量、每個(gè)連接的最大使用次數(shù)、最大空閑時(shí)間等等。也可以通過自身的管理機(jī)制來監(jiān)控?cái)?shù)據(jù)庫連接的數(shù)量和使用情況。
2. 數(shù)據(jù)庫連接池的運(yùn)行機(jī)制是什么樣的呢?
程序初始化時(shí)創(chuàng)建數(shù)據(jù)庫連接池,服務(wù)器啟動時(shí)建立數(shù)據(jù)庫連接池對象,按照實(shí)現(xiàn)設(shè)定的參數(shù)創(chuàng)建初始數(shù)量的數(shù)據(jù)庫連接也就是空閑連接數(shù)。
使用時(shí)向連接池申請可用連接,對于一個(gè)數(shù)據(jù)庫訪問請求,直接從連接池中得到一個(gè)連接。如果數(shù)據(jù)庫連接池對象中沒有空閑的連接,而且連接數(shù)沒有得到最大活躍連接數(shù),則創(chuàng)建一個(gè)新的數(shù)據(jù)庫連接。
使用完畢將連接返還給連接池。數(shù)據(jù)庫存取后關(guān)閉,釋放所有數(shù)據(jù)庫連接,此時(shí)的關(guān)閉數(shù)據(jù)庫連接并非真正關(guān)閉,而是將其放入空閑隊(duì)列中。如果實(shí)際空閑連接數(shù)大于初始空閑連接數(shù)則釋放連接。
程序退出時(shí)斷開所有連接并釋放資源,釋放數(shù)據(jù)庫連接池對象:服務(wù)器停止、維護(hù)期間釋放數(shù)據(jù)庫連接池對象并釋放所有連接。
數(shù)據(jù)庫連接池流程
連接池中的連接對象實(shí)際上是存放在內(nèi)存中的,在內(nèi)存中劃分出一塊緩存對象,應(yīng)用程序每次從池中獲得連接對象而不是直接從數(shù)據(jù)庫獲取,這樣不占用服務(wù)器的內(nèi)存資源。
3. 數(shù)據(jù)庫連接池帶來的好處是什么呢?
首先來看下如果不使用連接池會出現(xiàn)什么樣的情況:占用服務(wù)器的內(nèi)存資源導(dǎo)致服務(wù)器速度非常慢
資源重用,由于數(shù)據(jù)庫連接得到重用,避免了頻繁地創(chuàng)建、釋放連接引起的大量性能開銷。在減少系統(tǒng)消耗的基礎(chǔ)上,另一方面也增進(jìn)了系統(tǒng)運(yùn)行環(huán)境的平穩(wěn)性,減少內(nèi)存碎片以及數(shù)據(jù)庫臨時(shí)進(jìn)程/線程的數(shù)量。
更快的系統(tǒng)響應(yīng)速度,數(shù)據(jù)庫連接池在初始化過程中,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫連接置于池中備用。此時(shí)連接的初始化工作均已完成。對于業(yè)務(wù)請求處理而言,直接利用現(xiàn)有可用連接,避免了數(shù)據(jù)庫連接初始化和釋放過程的時(shí)間開銷,從而縮減了系統(tǒng)整體響應(yīng)事件。
新的資源分配手段,對于多應(yīng)用共享同一數(shù)據(jù)庫的系統(tǒng)而言,可在應(yīng)用層通過數(shù)據(jù)庫連接的配置,實(shí)現(xiàn)數(shù)據(jù)庫連接池。
統(tǒng)一的連接管理避免數(shù)據(jù)庫連接泄漏,在數(shù)據(jù)庫連接池中可根據(jù)預(yù)先的連接占用超時(shí)設(shè)定,強(qiáng)制收回被占用連接,從而避免了常規(guī)數(shù)據(jù)庫練級操作中可能出現(xiàn)的資源泄漏。
4. 數(shù)據(jù)庫連接池的影響因素是什么呢?
數(shù)據(jù)庫連接池在初始化時(shí)創(chuàng)建一定數(shù)量的連接并放入連接池中,這些連接的數(shù)量是由最小數(shù)據(jù)庫連接數(shù)制約的。無論這些連接是否被使用,連接池都將一致保存至少擁有這么多的連接數(shù)量。連接池的最大數(shù)據(jù)庫連接數(shù)量限定了連接池能占有的最大連接數(shù),當(dāng)應(yīng)用程序向連接池請求的連接數(shù)量超過最大連接數(shù)量時(shí),這些請求將被加入到等待隊(duì)列中。
數(shù)據(jù)庫連接池的最小連接數(shù)和最大連接數(shù)的設(shè)置要考慮到以下幾個(gè)因素:
- 最小連接數(shù)量是連接池一直保持的數(shù)據(jù)庫連接,如果應(yīng)用程序?qū)?shù)據(jù)庫連接的使用量不大,將會有大量的數(shù)據(jù)庫連接資源被浪費(fèi)。
- 最大連接數(shù)量是連接池能申請到的最大連接數(shù),如果數(shù)據(jù)庫連接請求超過此參數(shù),后面的數(shù)據(jù)庫連接請求將會被加入到等待隊(duì)列中,這會影響之后的數(shù)據(jù)庫操作。
- 最小連接數(shù)量與最大連接數(shù)量相差太大的話,那么最先的連接請求將會獲利,之后超過最小連接數(shù)量的連接請求等價(jià)于創(chuàng)建一個(gè)新的數(shù)據(jù)庫連接。不過,這些大于最小連接數(shù)的連接在使用完畢后不會立即被釋放掉,它們將會被放到連接池中等待重復(fù)使用或是空閑超時(shí)后被釋放。
二、運(yùn)行機(jī)制區(qū)別
1. 不使用連接池流程
下面以訪問MySQL為例,執(zhí)行一個(gè)SQL命令,如果不使用連接池,需要經(jīng)過哪些流程。
不使用數(shù)據(jù)庫連接池的步驟:
- TCP建立連接的三次握手
- MySQL認(rèn)證的三次握手
- 真正的SQL執(zhí)行
- MySQL的關(guān)閉
- TCP的四次握手關(guān)閉
可以看到,為了執(zhí)行一條SQL,卻多了非常多我們不關(guān)心的網(wǎng)絡(luò)交互。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單
缺點(diǎn):
- 網(wǎng)絡(luò)IO較多
- 數(shù)據(jù)庫的負(fù)載較高
- 響應(yīng)時(shí)間較長及QPS較低
- 應(yīng)用頻繁的創(chuàng)建連接和關(guān)閉連接,導(dǎo)致臨時(shí)對象較多,GC頻繁
- 在關(guān)閉連接后,會出現(xiàn)大量TIME_WAIT 的TCP狀態(tài)(在2個(gè)MSL之后關(guān)閉)
2. 使用連接池流程
使用數(shù)據(jù)庫連接池的步驟:第一次訪問的時(shí)候,需要建立連接。但是之后的訪問,均會復(fù)用之前創(chuàng)建的連接,直接執(zhí)行SQL語句。
優(yōu)點(diǎn):
- 較少了網(wǎng)絡(luò)開銷
- 系統(tǒng)的性能會有一個(gè)實(shí)質(zhì)的提升
- 沒了麻煩的TIME_WAIT狀態(tài)
三、數(shù)據(jù)庫連接池的工作原理
連接池的工作原理主要由三部分組成,分別為:
- 連接池的建立
- 連接池中連接的使用管理
- 連接池的關(guān)閉
第一、連接池的建立。 一般在系統(tǒng)初始化時(shí),連接池會根據(jù)系統(tǒng)配置建立,并在池中創(chuàng)建了幾個(gè)連接對象,以便使用時(shí)能從連接池中獲取。連接池中的連接不能隨意創(chuàng)建和關(guān)閉,這樣避免了連接隨意建立和關(guān)閉造成的系統(tǒng)開銷。Java中提供了很多容器類可以方便的構(gòu)建連接池,例如Vector、Stack等。
第二、連接池的管理。 連接池管理策略是連接池機(jī)制的核心,連接池內(nèi)連接的分配和釋放對系統(tǒng)的性能有很大的影響。
其管理策略是:
當(dāng)客戶請求數(shù)據(jù)庫連接時(shí),首先查看連接池中是否有空閑連接,如果存在空閑連接,則將連接分配給客戶使用;如果沒有空閑連接,則查看當(dāng)前所開的連接數(shù)是否已經(jīng)達(dá)到最大連接數(shù),如果沒達(dá)到就重新創(chuàng)建一個(gè)連接給請求的客戶;如果達(dá)到就按設(shè)定的最大等待時(shí)間進(jìn)行等待,如果超出最大等待時(shí)間,則拋出異常給客戶。當(dāng)客戶釋放數(shù)據(jù)庫連接時(shí),先判斷該連接的引用次數(shù)是否超過了規(guī)定值,如果超過就從連接池中刪除該連接,否則保留為其他客戶服務(wù)。該策略保證了數(shù)據(jù)庫連接的有效復(fù)用,避免頻繁的建立、釋放連接所帶來的系統(tǒng)資源開銷。
第三、連接池的關(guān)閉。 當(dāng)應(yīng)用程序退出時(shí),關(guān)閉連接池中所有的連接,釋放連接池相關(guān)的資源,以便連接可以返回池中重復(fù)利用。我們可以通過Connection對象的Close或Dispose方法,也可以通過C#的using語句來關(guān)閉連接。該過程正好與創(chuàng)建相反。
注意: 移除無效連接
無效連接,即不能正確連接到數(shù)據(jù)庫服務(wù)器的連接。對于連接池來說,存儲的與數(shù)據(jù)庫服務(wù)器的連接的數(shù)量是有限的。因此,對于無效連接,如果如不及時(shí)移除,將會浪費(fèi)連接池的空間。其實(shí)你不用擔(dān)心,連接池管理器已經(jīng)很好的為我們處理了這些問題。如果連接長時(shí)間空閑,或檢測到與服務(wù)器的連接已斷開,連接池管理器會將該連接從池中移除。
1. 連接池的誕生
數(shù)據(jù)庫連接的創(chuàng)建和銷毀操作之所以開銷巨大,是因?yàn)檫@不僅僅是簡單的建立或斷開一個(gè)通道。以常見的關(guān)系型數(shù)據(jù)庫 MySQL 為例,當(dāng)應(yīng)用程序請求創(chuàng)建一個(gè)數(shù)據(jù)庫連接時(shí),首先要進(jìn)行 TCP 三次握手建立網(wǎng)絡(luò)連接,這個(gè)過程涉及到網(wǎng)絡(luò)層和傳輸層的交互,會消耗一定的時(shí)間和網(wǎng)絡(luò)資源。接著,數(shù)據(jù)庫服務(wù)器需要對客戶端進(jìn)行身份驗(yàn)證,驗(yàn)證用戶名和密碼是否正確,這涉及到數(shù)據(jù)庫內(nèi)部的權(quán)限管理系統(tǒng),需要查詢相關(guān)的數(shù)據(jù)表和進(jìn)行加密解密操作。成功驗(yàn)證后,數(shù)據(jù)庫服務(wù)器還需要為該連接分配內(nèi)存等資源,用于存儲連接狀態(tài)、執(zhí)行 SQL 語句的上下文信息等 。當(dāng)連接使用完畢進(jìn)行銷毀時(shí),同樣需要進(jìn)行一系列的資源釋放和狀態(tài)清理工作。
在高并發(fā)場景下,假設(shè)一個(gè)電商系統(tǒng)在促銷活動期間,每秒有數(shù)千個(gè)用戶同時(shí)進(jìn)行商品查詢、下單等操作,如果每次操作都新建和銷毀數(shù)據(jù)庫連接,系統(tǒng)的 CPU 將大部分時(shí)間都耗費(fèi)在這些連接管理操作上,而不是真正執(zhí)行數(shù)據(jù)庫查詢和業(yè)務(wù)邏輯。這會導(dǎo)致系統(tǒng)響應(yīng)時(shí)間大幅增加,原本可能幾毫秒就能完成的數(shù)據(jù)庫查詢,由于連接創(chuàng)建和銷毀的開銷,可能會延長到幾百毫秒甚至秒級,用戶在頁面上點(diǎn)擊查詢后,長時(shí)間得不到響應(yīng),極大地影響用戶體驗(yàn)。同時(shí),頻繁的連接創(chuàng)建和銷毀還可能導(dǎo)致數(shù)據(jù)庫服務(wù)器的資源耗盡,如內(nèi)存不足、文件描述符用盡等問題,進(jìn)而引發(fā)整個(gè)系統(tǒng)的崩潰。
連接池的出現(xiàn),就像是為數(shù)據(jù)庫連接找到了一個(gè)高效的 “管家”。它通過復(fù)用連接,在系統(tǒng)初始化時(shí)就預(yù)先創(chuàng)建一定數(shù)量的連接,并將這些連接存儲在一個(gè) “池子” 里。當(dāng)應(yīng)用程序需要連接時(shí),直接從池子里獲取,使用完后再歸還到池子中。這樣就避免了重復(fù)進(jìn)行昂貴的連接創(chuàng)建和銷毀操作,大大節(jié)省了系統(tǒng)資源,提高了系統(tǒng)的響應(yīng)速度和吞吐量。例如,在上述電商系統(tǒng)中,使用連接池后,連接獲取的時(shí)間可以從幾十毫秒甚至幾百毫秒降低到幾毫秒,系統(tǒng)能夠輕松應(yīng)對高并發(fā)的請求,保證了系統(tǒng)的穩(wěn)定運(yùn)行和良好的用戶體驗(yàn)。
(1) DBCP連接池
DBCP 是 Apache 軟件基金組織下的開源連接池實(shí)現(xiàn),使用DBCP數(shù)據(jù)源,應(yīng)用程序應(yīng)在系統(tǒng)中增加如下兩個(gè) jar 文件:
- Commons-dbcp.jar:連接池的實(shí)現(xiàn)
- Commons-pool.jar:連接池實(shí)現(xiàn)的依賴庫
Tomcat 的連接池正是采用該連接池來實(shí)現(xiàn)的。該數(shù)據(jù)庫連接池既可以與應(yīng)用服務(wù)器整合使用,也可由應(yīng)用程序獨(dú)立使用。
核心類:BasicDataSource
使用步驟:
- 引入jar文件commons-dbcp-1.4.jar
- commons-pool-1.5.6.jar
public class App_DBCP {
// 1. 硬編碼方式實(shí)現(xiàn)連接池
@Test
public void testDbcp() throws Exception {
// DBCP連接池核心類
BasicDataSource dataSouce = new BasicDataSource();
// 連接池參數(shù)配置:初始化連接數(shù)、最大連接數(shù) / 連接字符串、驅(qū)動、用戶、密碼
dataSouce.setUrl("jdbc:mysql:///jdbc_demo"); //數(shù)據(jù)庫連接字符串
dataSouce.setDriverClassName("com.mysql.jdbc.Driver"); //數(shù)據(jù)庫驅(qū)動
dataSouce.setUsername("root"); //數(shù)據(jù)庫連接用戶
dataSouce.setPassword("root"); //數(shù)據(jù)庫連接密碼
dataSouce.setInitialSize(3); // 初始化連接
dataSouce.setMaxActive(6); // 最大連接
dataSouce.setMaxIdle(3000); // 最大空閑時(shí)間
// 獲取連接
Connection con = dataSouce.getConnection();
con.prepareStatement("delete from admin where id=3").executeUpdate();
// 關(guān)閉
con.close();
}
@Test
// 2. 【推薦】配置方式實(shí)現(xiàn)連接池 , 便于維護(hù)
public void testProp() throws Exception {
// 加載prop配置文件
Properties prop = new Properties();
// 獲取文件流
InputStream inStream = App_DBCP.class.getResourceAsStream("db.properties");
// 加載屬性配置文件
prop.load(inStream);
// 根據(jù)prop配置,直接創(chuàng)建數(shù)據(jù)源對象
DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);
// 獲取連接
Connection con = dataSouce.getConnection();
con.prepareStatement("delete from admin where id=4").executeUpdate();
// 關(guān)閉
con.close();
}
}
配置方式實(shí)現(xiàn)DBCP連接池, 配置文件中的key與BaseDataSouce中的屬性一樣:
#基本配置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb1
username=root
password=123
#初始化池大小,即一開始池中就會有10個(gè)連接對象
默認(rèn)值為0
initialSize=0
#最大連接數(shù),如果設(shè)置maxActive=50時(shí),池中最多可以有50個(gè)連接,當(dāng)然這50個(gè)連接中包含被使用的和沒被使用的(空閑)
#你是一個(gè)包工頭,你一共有50個(gè)工人,但這50個(gè)工人有的當(dāng)前正在工作,有的正在空閑
#默認(rèn)值為8,如果設(shè)置為非正數(shù),表示沒有限制!即無限大
maxActive=8
#最大空閑連接
#當(dāng)設(shè)置maxIdle=30時(shí),你是包工頭,你允許最多有20個(gè)工人空閑,如果現(xiàn)在有30個(gè)空閑工人,那么要開除10個(gè)
#默認(rèn)值為8,如果設(shè)置為負(fù)數(shù),表示沒有限制!即無限大
maxIdle=8
#最小空閑連接
#如果設(shè)置minIdel=5時(shí),如果你的工人只有3個(gè)空閑,那么你需要再去招2個(gè)回來,保證有5個(gè)空閑工人
#默認(rèn)值為0
minIdle=0
#最大等待時(shí)間
#當(dāng)設(shè)置maxWait=5000時(shí),現(xiàn)在你的工作都出去工作了,又來了一個(gè)工作,需要一個(gè)工人。
#這時(shí)就要等待有工人回來,如果等待5000毫秒還沒回來,那就拋出異常
#沒有工人的原因:最多工人數(shù)為50,已經(jīng)有50個(gè)工人了,不能再招了,但50人都出去工作了。
#默認(rèn)值為-1,表示無限期等待,不會拋出異常。
maxWait=-1
#連接屬性
#就是原來放在url后面的參數(shù),可以使用connectionProperties來指定
#如果已經(jīng)在url后面指定了,那么就不用在這里指定了。
#useServerPrepStmts=true,MySQL開啟預(yù)編譯功能
#cachePrepStmts=true,MySQL開啟緩存PreparedStatement功能,
#prepStmtCacheSize=50,緩存PreparedStatement的上限
#prepStmtCacheSqlLimit=300,當(dāng)SQL模板長度大于300時(shí),就不再緩存它
cnotallow=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300
#連接的默認(rèn)提交方式
#默認(rèn)值為true
defaultAutoCommit=true
#連接是否為只讀連接
#Connection有一對方法:setReadOnly(boolean)和isReadOnly()
#如果是只讀連接,那么你只能用這個(gè)連接來做查詢
#指定連接為只讀是為了優(yōu)化!這個(gè)優(yōu)化與并發(fā)事務(wù)相關(guān)!
#如果兩個(gè)并發(fā)事務(wù),對同一行記錄做增、刪、改操作,是不是一定要隔離它們啊?
#如果兩個(gè)并發(fā)事務(wù),對同一行記錄只做查詢操作,那么是不是就不用隔離它們了?
#如果沒有指定這個(gè)屬性值,那么是否為只讀連接,這就由驅(qū)動自己來決定了。即Connection的實(shí)現(xiàn)類自己來決定!
defaultReadOnly=false
#指定事務(wù)的事務(wù)隔離級別
#可選值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
#如果沒有指定,那么由驅(qū)動中的Connection實(shí)現(xiàn)類自己來決定
defaultTransactinotallow=REPEATABLE_READ
(2) C3P0連接池
- C3P0連接池:最常用的連接池技術(shù)!Spring框架,默認(rèn)支持C3P0連接池技術(shù)!
- C3P0連接池,核心類:CombopooledDataSource ds
使用:下載,引入jar文件: c3p0-0.9.1.2.jar
使用連接池,創(chuàng)建連接:
- 硬編碼方式
- 配置方式(xml)
配置文件要求:
- 文件名稱:必須叫c3p0-config.xml
- 文件位置:必須在src下
c3p0也可以指定配置文件,而且配置文件可以是properties,也可以 xml的。當(dāng)然xml的高級一些了。但是c3p0的配置文件名必須為c3p0-config.xml,并且必須放在類路徑下。
下面是源碼:
<?xml versinotallow="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</default-config>
<named-config name="oracle-config">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</named-config>
</c3p0-config>
public class App {
@Test
//1. 硬編碼方式,使用C3P0連接池管理連接
public void testCode() throws Exception {
// 創(chuàng)建連接池核心工具類
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 設(shè)置連接參數(shù):url、驅(qū)動、用戶密碼、初始連接數(shù)、最大連接數(shù)
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_demo");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setInitialPoolSize(3);
dataSource.setMaxPoolSize(6);
dataSource.setMaxIdleTime(1000);
// ---> 從連接池對象中,獲取連接對象
Connection con = dataSource.getConnection();
// 執(zhí)行更新
con.prepareStatement("delete from admin where id=7").executeUpdate();
// 關(guān)閉
con.close();
}
@Test
//2. XML配置方式,使用C3P0連接池管理連接
public void testXML() throws Exception {
// 創(chuàng)建c3p0連接池核心工具類
// 自動加載src下c3p0的配置文件【c3p0-config.xml】
ComboPooledDataSource dataSource = new ComboPooledDataSource();// 使用默認(rèn)的配置
// 獲取連接
Connection con = dataSource.getConnection();
// 執(zhí)行更新
con.prepareStatement("delete from admin where id=5").executeUpdate();
// 關(guān)閉
con.close();
} //獲取配置文件中“orcale-cofig"的配置信息
public void fun2() throws PropertyVetoException, SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource("orcale-config");
Connection con = ds.getConnection();
System.out.println(con);
con.close();
}
}
2. 核心組件與運(yùn)作機(jī)制
連接池包含多個(gè)核心組件,每個(gè)組件都在連接池的高效運(yùn)作中發(fā)揮著關(guān)鍵作用。連接容器是連接池的基礎(chǔ)組成部分,它就像一個(gè)倉庫,用于存儲和管理數(shù)據(jù)庫連接。常見的連接容器可以是一個(gè)集合,如 Java 中的 ArrayList 或 LinkedList,也可以是更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),如線程安全的 ConcurrentLinkedQueue。這些數(shù)據(jù)結(jié)構(gòu)能夠方便地進(jìn)行連接的添加、移除和查找操作。
連接池管理器則是連接池的 “指揮官”,負(fù)責(zé)連接池的整體管理和協(xié)調(diào)。它維護(hù)著連接池的各種狀態(tài)信息,比如當(dāng)前連接池中的連接總數(shù)、空閑連接數(shù)、正在使用的連接數(shù)等。連接池管理器根據(jù)這些狀態(tài)信息以及預(yù)先設(shè)定的配置策略,來決定何時(shí)創(chuàng)建新的連接、何時(shí)銷毀空閑連接以及如何分配連接給應(yīng)用程序。例如,當(dāng)應(yīng)用程序請求連接時(shí),連接池管理器會檢查連接容器中是否有空閑連接,如果有,則直接將空閑連接分配給應(yīng)用程序;如果沒有空閑連接,且當(dāng)前連接數(shù)未達(dá)到最大連接數(shù)限制,那么連接池管理器會創(chuàng)建新的連接并分配給應(yīng)用程序。
連接池的運(yùn)作過程涵蓋了連接獲取、釋放、創(chuàng)建和銷毀等多個(gè)關(guān)鍵操作。當(dāng)應(yīng)用程序需要數(shù)據(jù)庫連接時(shí),會向連接池發(fā)起獲取連接的請求。連接池管理器首先檢查連接容器中是否有空閑連接,如果有,就從連接容器中取出一個(gè)空閑連接,并將其標(biāo)記為正在使用狀態(tài),然后返回給應(yīng)用程序。如果連接容器中沒有空閑連接,連接池管理器會根據(jù)配置策略進(jìn)行處理。如果當(dāng)前連接數(shù)小于最大連接數(shù),連接池管理器會創(chuàng)建新的連接,將其標(biāo)記為正在使用狀態(tài)后返回給應(yīng)用程序;如果當(dāng)前連接數(shù)已經(jīng)達(dá)到最大連接數(shù),應(yīng)用程序可能需要等待,直到有其他連接被釋放回連接池,或者等待超時(shí)后拋出異常。
當(dāng)應(yīng)用程序使用完數(shù)據(jù)庫連接后,會將連接釋放回連接池。連接池管理器接收到釋放的連接后,會將其標(biāo)記為空閑狀態(tài),并將其放回連接容器中,供后續(xù)的請求復(fù)用。在連接的使用過程中,連接池還會定期對連接進(jìn)行檢測,確保連接的有效性。如果發(fā)現(xiàn)某個(gè)連接已經(jīng)失效,比如因?yàn)榫W(wǎng)絡(luò)故障或數(shù)據(jù)庫服務(wù)器重啟導(dǎo)致連接斷開,連接池會將該連接從連接容器中移除,并銷毀該連接資源,以避免無效連接占用資源。
連接池的創(chuàng)建操作通常在系統(tǒng)初始化階段進(jìn)行。根據(jù)配置的初始連接數(shù),連接池管理器會創(chuàng)建相應(yīng)數(shù)量的數(shù)據(jù)庫連接,并將它們存儲在連接容器中。在系統(tǒng)運(yùn)行過程中,如果連接池中的連接數(shù)量不足,連接池管理器也會根據(jù)需要創(chuàng)建新的連接。而連接的銷毀操作則是在連接不再需要時(shí)進(jìn)行。比如,當(dāng)連接池中的空閑連接數(shù)量超過了配置的最大空閑連接數(shù),連接池管理器會選擇一些空閑時(shí)間較長的連接進(jìn)行銷毀,以釋放資源;當(dāng)系統(tǒng)關(guān)閉時(shí),連接池管理器會銷毀連接池中的所有連接,確保資源的正確釋放。
四、設(shè)計(jì)要點(diǎn):打造高性能連接池
1. 連接池大小的抉擇
- 最小連接數(shù)Min Pool Size:默認(rèn)為0。是連接池一直保持的數(shù)據(jù)庫連接,所以如果應(yīng)用程序?qū)?shù)據(jù)庫連接的使用量不大,將會有大量的數(shù)據(jù)庫連接資源被浪費(fèi)
- 最大連接數(shù)Max Pool Size:默認(rèn)為100。:是連接池能申請的最大連接數(shù),如果數(shù)據(jù)庫連接請求超過次數(shù),后面的數(shù)據(jù)庫連接請求將被加入到等待隊(duì)列中,這會影響以后的數(shù)據(jù)庫操作
- 最大空閑時(shí)間獲取連接超時(shí)時(shí)間Connection Timeout:連接請求等待超時(shí)時(shí)間。默認(rèn)為15秒,單位為秒。
- 超時(shí)重試連接次數(shù)Pooling:是否啟用連接池。http://ADO.NET默認(rèn)是啟用連接池的,因此,你需要手動設(shè)置Pooling=false來禁用連接池。
這里放一個(gè)我們項(xiàng)目druid的配置:
連接池大小的設(shè)置是連接池設(shè)計(jì)中至關(guān)重要的一環(huán),它直接關(guān)系到系統(tǒng)在高并發(fā)場景下的性能表現(xiàn)。如果連接池大小設(shè)置得過小,當(dāng)大量并發(fā)請求涌入時(shí),連接池中的連接很快就會被耗盡,后續(xù)的請求就不得不進(jìn)入等待隊(duì)列,等待其他連接被釋放后才能獲取到連接進(jìn)行數(shù)據(jù)庫操作。這就好比一家熱門餐廳,只有少量的餐桌(連接),用餐高峰時(shí)(高并發(fā)場景),大量顧客(請求)只能在門口排隊(duì)等待,導(dǎo)致顧客等待時(shí)間過長,體驗(yàn)感極差。在實(shí)際的電商系統(tǒng)中,可能會出現(xiàn)用戶在促銷活動期間下單時(shí),由于連接池大小不足,長時(shí)間等待訂單處理結(jié)果,甚至出現(xiàn)超時(shí)錯誤,這不僅影響用戶購買的積極性,還可能導(dǎo)致訂單丟失,給商家?guī)斫?jīng)濟(jì)損失。
相反,如果連接池大小設(shè)置得過大,雖然可以滿足大量并發(fā)請求的連接需求,但會消耗過多的系統(tǒng)資源。每個(gè)數(shù)據(jù)庫連接都需要占用一定的內(nèi)存、網(wǎng)絡(luò)資源以及數(shù)據(jù)庫服務(wù)器的資源。過多的連接會導(dǎo)致服務(wù)器內(nèi)存緊張,增加內(nèi)存管理的開銷,甚至可能引發(fā)內(nèi)存溢出等問題。同時(shí),過多的連接還可能對數(shù)據(jù)庫服務(wù)器造成過大的壓力,影響數(shù)據(jù)庫的性能和穩(wěn)定性。例如,在一個(gè)企業(yè)級應(yīng)用中,如果連接池大小設(shè)置不合理,過大的連接池會使數(shù)據(jù)庫服務(wù)器忙于處理大量的連接請求,而無法高效地執(zhí)行真正的業(yè)務(wù)查詢,導(dǎo)致數(shù)據(jù)庫響應(yīng)變慢,整個(gè)系統(tǒng)的性能也隨之下降。
為了確定合適的連接池大小,需要綜合考慮多個(gè)因素。首先,要深入分析應(yīng)用的并發(fā)需求。可以通過性能測試工具模擬不同并發(fā)場景下的請求量,觀察系統(tǒng)的性能指標(biāo),如響應(yīng)時(shí)間、吞吐量等,來確定系統(tǒng)在不同負(fù)載下所需的最佳連接數(shù)。同時(shí),還需要了解數(shù)據(jù)庫服務(wù)器的處理能力,包括服務(wù)器的硬件配置(如 CPU、內(nèi)存、磁盤 I/O 等)以及數(shù)據(jù)庫的并發(fā)處理能力。如果數(shù)據(jù)庫服務(wù)器的硬件配置較低,或者數(shù)據(jù)庫本身對并發(fā)連接的支持有限,那么即使設(shè)置了很大的連接池大小,也無法充分利用,反而會浪費(fèi)資源。此外,還可以參考一些經(jīng)驗(yàn)公式,如連接池大小 = (2 * CPU 核數(shù)) + 1,但這只是一個(gè)大致的參考,實(shí)際應(yīng)用中還需要根據(jù)具體情況進(jìn)行調(diào)整和優(yōu)化。
2. 連接生命周期管理
連接生命周期管理是連接池設(shè)計(jì)中的另一個(gè)關(guān)鍵方面,它主要涉及到最小和最大連接數(shù)、連接空閑超時(shí)、連接回收等參數(shù)的設(shè)置,這些參數(shù)對于確保連接池的高效運(yùn)行和資源的合理利用起著至關(guān)重要的作用。
最小連接數(shù)是連接池在空閑狀態(tài)下始終保持的連接數(shù)量。設(shè)置合適的最小連接數(shù)可以避免在高并發(fā)請求突然到來時(shí),因?yàn)樾枰R時(shí)創(chuàng)建大量連接而導(dǎo)致的性能延遲。例如,在一個(gè)實(shí)時(shí)監(jiān)控系統(tǒng)中,可能會有持續(xù)的少量請求不斷地查詢數(shù)據(jù)庫獲取最新的監(jiān)控?cái)?shù)據(jù)。如果最小連接數(shù)設(shè)置得過小,每次請求都需要創(chuàng)建新的連接,這會增加連接建立的時(shí)間開銷,導(dǎo)致監(jiān)控?cái)?shù)據(jù)的獲取不及時(shí)。而設(shè)置較大的最小連接數(shù),可以保證在請求到來時(shí),有足夠的空閑連接可供使用,提高系統(tǒng)的響應(yīng)速度。但是,如果最小連接數(shù)設(shè)置過大,會導(dǎo)致在系統(tǒng)空閑時(shí),仍然占用大量的數(shù)據(jù)庫連接資源,浪費(fèi)系統(tǒng)資源。
最大連接數(shù)則限制了連接池中可以同時(shí)存在的最大連接數(shù)量。它是防止系統(tǒng)資源被過度消耗的重要防線。當(dāng)并發(fā)請求數(shù)量超過最大連接數(shù)時(shí),新的請求將無法立即獲取到連接,需要等待其他連接被釋放。合理設(shè)置最大連接數(shù)可以避免因連接過多而導(dǎo)致數(shù)據(jù)庫服務(wù)器資源耗盡。比如,在一個(gè)在線教育平臺中,同時(shí)有大量學(xué)生進(jìn)行課程學(xué)習(xí)、作業(yè)提交等操作,如果沒有設(shè)置合理的最大連接數(shù),過多的連接可能會使數(shù)據(jù)庫服務(wù)器不堪重負(fù),出現(xiàn)死機(jī)或崩潰的情況,影響整個(gè)平臺的正常運(yùn)行。
連接空閑超時(shí)是指連接在池中空閑的最長時(shí)間,超過這個(gè)時(shí)間,連接將被回收。這一參數(shù)的設(shè)置可以有效地避免連接長時(shí)間占用資源而不被使用的情況。在一個(gè)企業(yè)的辦公自動化系統(tǒng)中,可能會有一些用戶長時(shí)間登錄系統(tǒng),但實(shí)際操作并不頻繁,其對應(yīng)的數(shù)據(jù)庫連接可能會長時(shí)間處于空閑狀態(tài)。如果沒有設(shè)置連接空閑超時(shí),這些空閑連接會一直占用資源,導(dǎo)致連接池中的有效連接減少。通過設(shè)置合理的連接空閑超時(shí),當(dāng)連接空閑時(shí)間超過設(shè)定值時(shí),連接池會自動回收這些連接,釋放資源,供其他需要的請求使用。
連接回收機(jī)制是連接生命周期管理的重要組成部分。除了根據(jù)空閑超時(shí)回收連接外,還可以定期對連接池中的連接進(jìn)行健康檢查,對于那些已經(jīng)失效的連接,如因?yàn)榫W(wǎng)絡(luò)故障或數(shù)據(jù)庫服務(wù)器重啟導(dǎo)致連接斷開的連接,及時(shí)進(jìn)行回收和清理。這樣可以保證連接池中的連接都是有效的,避免將無效連接分配給應(yīng)用程序,從而提高系統(tǒng)的穩(wěn)定性和可靠性。
3. 并發(fā)控制策略
在高并發(fā)場景下,連接池面臨著嚴(yán)峻的并發(fā)訪問挑戰(zhàn)。多個(gè)線程同時(shí)請求獲取和釋放連接,可能會導(dǎo)致數(shù)據(jù)不一致、連接泄漏、資源競爭等問題。為了確保連接池的線程安全性和高效運(yùn)行,需要采用有效的并發(fā)控制策略。
鎖機(jī)制是實(shí)現(xiàn)并發(fā)控制的常用手段之一。在連接池中,可以使用互斥鎖(如 Java 中的 synchronized 關(guān)鍵字或 ReentrantLock)來保證在同一時(shí)刻只有一個(gè)線程能夠訪問連接池的關(guān)鍵資源,如連接的獲取、釋放和連接池狀態(tài)的修改等操作。當(dāng)一個(gè)線程獲取到鎖后,其他線程必須等待鎖的釋放才能進(jìn)行相應(yīng)的操作。
例如,在一個(gè)多線程的 Web 應(yīng)用中,當(dāng)多個(gè)線程同時(shí)請求從連接池中獲取連接時(shí),通過鎖機(jī)制可以確保每次只有一個(gè)線程能夠成功獲取連接,避免了多個(gè)線程同時(shí)操作連接池導(dǎo)致的混亂。然而,鎖機(jī)制也存在一定的局限性,它會引入額外的開銷,如線程上下文切換和鎖的競爭,可能會降低系統(tǒng)的并發(fā)性能。當(dāng)鎖的競爭激烈時(shí),會導(dǎo)致大量線程處于等待狀態(tài),增加線程的等待時(shí)間,降低系統(tǒng)的吞吐量。
為了減少鎖的競爭,提高并發(fā)性能,可以采用一些更細(xì)粒度的并發(fā)控制策略。例如,使用線程安全的數(shù)據(jù)結(jié)構(gòu)來管理連接池中的連接。像 Java 中的 ConcurrentLinkedQueue 就是一種線程安全的隊(duì)列,它可以在多線程環(huán)境下高效地進(jìn)行元素的添加和移除操作,無需使用鎖來保證線程安全。在連接池中,可以使用這種數(shù)據(jù)結(jié)構(gòu)來存儲空閑連接,當(dāng)線程請求獲取連接時(shí),可以直接從隊(duì)列中獲取,而不需要獲取鎖,從而提高并發(fā)性能。此外,還可以采用分段鎖的策略,將連接池劃分為多個(gè)段,每個(gè)段使用獨(dú)立的鎖進(jìn)行控制。當(dāng)不同線程請求不同段的連接時(shí),不會發(fā)生鎖的競爭,只有當(dāng)線程請求同一分段的連接時(shí)才會競爭鎖,這樣可以大大減少鎖的競爭范圍,提高系統(tǒng)的并發(fā)處理能力。
除了鎖機(jī)制和線程安全的數(shù)據(jù)結(jié)構(gòu),還可以采用信號量機(jī)制來控制并發(fā)訪問。信號量可以限制同時(shí)訪問某個(gè)資源的線程數(shù)量。在連接池中,可以設(shè)置一個(gè)信號量,其初始值為連接池的最大連接數(shù)。當(dāng)一個(gè)線程請求獲取連接時(shí),首先嘗試獲取信號量,如果信號量的值大于 0,則表示有可用連接,線程可以獲取連接并將信號量的值減 1;如果信號量的值為 0,則表示連接池已滿,線程需要等待,直到有其他線程釋放連接并歸還信號量。這種機(jī)制可以有效地控制并發(fā)請求的數(shù)量,避免連接池被過度使用,同時(shí)也可以減少線程的等待時(shí)間,提高系統(tǒng)的性能。
五、優(yōu)化策略:讓連接池飛起來
1. 連接池參數(shù)調(diào)優(yōu)實(shí)戰(zhàn)
在實(shí)際應(yīng)用中,不同的連接池框架有著各自獨(dú)特的配置參數(shù),深入理解這些參數(shù)的含義并進(jìn)行合理優(yōu)化,是提升連接池性能的關(guān)鍵。以 HikariCP 和 Druid 這兩款廣泛使用的連接池框架為例,我們來詳細(xì)探討它們的常見參數(shù)及優(yōu)化方法。
HikariCP 作為一款高性能的連接池,其配置參數(shù)簡潔而高效。其中,maximumPoolSize(最大連接數(shù))決定了連接池中能夠容納的最大連接數(shù)量。在一個(gè)高并發(fā)的電商系統(tǒng)中,如果設(shè)置過小,如設(shè)置為 10,當(dāng)同時(shí)有 50 個(gè)用戶進(jìn)行商品查詢、下單等操作時(shí),超過 10 個(gè)請求就需要等待連接釋放,這會導(dǎo)致大量請求超時(shí),用戶體驗(yàn)極差。一般來說,對于中等規(guī)模的電商系統(tǒng),根據(jù)服務(wù)器硬件配置和業(yè)務(wù)并發(fā)量,maximumPoolSize可以設(shè)置在 50 - 100 之間。而minimumIdle(最小空閑連接數(shù))表示連接池中保持的最小空閑連接數(shù)量。若設(shè)置為 1,在業(yè)務(wù)高峰時(shí),可能會頻繁創(chuàng)建和銷毀連接,增加系統(tǒng)開銷。通常建議設(shè)置為 10 - 20,以保證在業(yè)務(wù)量波動時(shí),系統(tǒng)能夠快速響應(yīng)請求。
connectionTimeout(連接超時(shí)時(shí)間)用于設(shè)置從連接池獲取連接的最大等待時(shí)間,默認(rèn)值為 30000 毫秒(30 秒)。如果設(shè)置過短,如 5000 毫秒(5 秒),在高并發(fā)且連接池繁忙時(shí),應(yīng)用程序可能因?yàn)槎虝r(shí)間內(nèi)獲取不到連接而報(bào)錯,影響業(yè)務(wù)正常進(jìn)行。根據(jù)實(shí)際業(yè)務(wù)場景,可適當(dāng)調(diào)整為 10000 - 20000 毫秒,以平衡等待時(shí)間和系統(tǒng)響應(yīng)效率。
Druid 連接池功能豐富,除了基本的連接管理功能外,還提供了強(qiáng)大的監(jiān)控和擴(kuò)展功能。其maxActive(最大連接數(shù))與 HikariCP 的maximumPoolSize類似,控制著連接池的最大連接數(shù)量。在一個(gè)企業(yè)級的 OA 系統(tǒng)中,若maxActive設(shè)置為 30,當(dāng)多個(gè)部門同時(shí)進(jìn)行文件審批、數(shù)據(jù)查詢等操作時(shí),可能會出現(xiàn)連接不足的情況。
根據(jù) OA 系統(tǒng)的并發(fā)特點(diǎn)和用戶規(guī)模,可將maxActive設(shè)置在 30 - 50 之間。minIdle(最小空閑連接數(shù))同樣影響著系統(tǒng)的響應(yīng)速度和資源利用效率。若設(shè)置為 5,在系統(tǒng)空閑時(shí),可能會保留過多的空閑連接,浪費(fèi)資源;若設(shè)置為 1,在業(yè)務(wù)量突然增加時(shí),可能無法及時(shí)提供足夠的連接。一般可根據(jù)業(yè)務(wù)的平均負(fù)載情況,設(shè)置為 10 - 15。maxWait(最長等待時(shí)間)表示獲取連接時(shí)的最大等待時(shí)間,單位為毫秒。若設(shè)置為 10000 毫秒(10 秒),當(dāng)連接池滿且請求頻繁時(shí),部分請求可能會因?yàn)榈却龝r(shí)間過長而失敗??筛鶕?jù)業(yè)務(wù)對響應(yīng)時(shí)間的要求,調(diào)整為 15000 - 20000 毫秒,確保請求有足夠的等待時(shí)間獲取連接。
2. 連接有效性檢測與維護(hù)
在高并發(fā)場景下,確保連接池中的連接始終有效是至關(guān)重要的,這直接關(guān)系到系統(tǒng)的穩(wěn)定性和可靠性。定期檢測連接有效性能夠及時(shí)發(fā)現(xiàn)并處理失效連接,避免將無效連接分配給應(yīng)用程序,從而防止因連接問題導(dǎo)致的業(yè)務(wù)故障。
心跳檢測是一種常見的連接有效性檢測方法,它通過定期向數(shù)據(jù)庫發(fā)送一個(gè)簡單的心跳包,如一個(gè)輕量級的 SQL 查詢(如SELECT 1)或特定的數(shù)據(jù)庫命令(如 MySQL 的PING命令),來判斷連接是否仍然可用。如果數(shù)據(jù)庫能夠正常響應(yīng)心跳包,則說明連接有效;若在規(guī)定時(shí)間內(nèi)未收到響應(yīng),則認(rèn)為連接已失效。以一個(gè)在線游戲服務(wù)器為例,每 5 秒向數(shù)據(jù)庫發(fā)送一次心跳包,若連續(xù) 3 次未收到響應(yīng),則判定連接失效,將該連接從連接池中移除,并重新創(chuàng)建新的連接。這種方式能夠快速檢測出連接的異常情況,保證連接池中的連接始終處于可用狀態(tài)。
SQL 查詢檢測則是通過執(zhí)行一些特定的 SQL 查詢來驗(yàn)證連接的有效性。這些查詢通常選擇那些對數(shù)據(jù)庫資源消耗較小,但又能準(zhǔn)確反映數(shù)據(jù)庫狀態(tài)的語句,如查詢一個(gè)固定的系統(tǒng)表或執(zhí)行一個(gè)簡單的計(jì)數(shù)查詢(如SELECT COUNT(*) FROM some_table)。在一個(gè)金融交易系統(tǒng)中,每隔 10 秒執(zhí)行一次 SQL 查詢檢測連接有效性,若查詢失敗或返回異常結(jié)果,則認(rèn)為連接失效,立即進(jìn)行處理。通過這種方式,可以確保連接池中的連接能夠正常執(zhí)行數(shù)據(jù)庫操作,避免因連接問題導(dǎo)致的交易錯誤或數(shù)據(jù)不一致。
除了檢測連接有效性,及時(shí)移除失效連接也是維護(hù)連接池健康的關(guān)鍵環(huán)節(jié)。當(dāng)檢測到連接失效時(shí),連接池應(yīng)立即將其從連接容器中移除,并釋放相關(guān)的資源,如關(guān)閉網(wǎng)絡(luò)連接、釋放數(shù)據(jù)庫資源等。同時(shí),為了保證連接池的性能,還可以在移除失效連接后,根據(jù)配置策略及時(shí)創(chuàng)建新的連接,以確保連接池中的連接數(shù)量始終滿足業(yè)務(wù)需求。例如,在一個(gè)大型電商平臺的連接池中,當(dāng)檢測到某個(gè)連接失效后,會立即將其移除,并根據(jù)當(dāng)前連接池的狀態(tài)和業(yè)務(wù)負(fù)載情況,決定是否創(chuàng)建新的連接。如果當(dāng)前連接池中的連接數(shù)量低于最小空閑連接數(shù),則會立即創(chuàng)建新的連接,以保證系統(tǒng)的正常運(yùn)行。
3. 事務(wù)管理與連接池的協(xié)同
事務(wù)管理在高并發(fā)場景下對于保證數(shù)據(jù)的一致性和完整性起著不可或缺的作用,而連接池與事務(wù)管理的協(xié)同工作則是確保系統(tǒng)高效穩(wěn)定運(yùn)行的關(guān)鍵因素。
在高并發(fā)環(huán)境中,多個(gè)事務(wù)可能同時(shí)對數(shù)據(jù)庫進(jìn)行操作,如果事務(wù)管理不當(dāng),可能會導(dǎo)致數(shù)據(jù)不一致、臟讀、幻讀等問題。例如,在一個(gè)銀行轉(zhuǎn)賬系統(tǒng)中,當(dāng)用戶 A 向用戶 B 轉(zhuǎn)賬時(shí),涉及到兩個(gè)賬戶的資金變動,這兩個(gè)操作必須作為一個(gè)事務(wù)來處理,要么都成功,要么都失敗。如果在高并發(fā)情況下,事務(wù)管理出現(xiàn)問題,可能會導(dǎo)致 A 賬戶的錢扣除了,但 B 賬戶卻沒有收到錢,從而造成數(shù)據(jù)不一致。因此,正確的事務(wù)管理能夠保證在高并發(fā)場景下,數(shù)據(jù)庫操作的原子性、一致性、隔離性和持久性。
在連接池中管理事務(wù)的生命周期需要遵循一定的原則和方法。首先,事務(wù)的開始和結(jié)束應(yīng)該與連接的獲取和釋放緊密配合。當(dāng)應(yīng)用程序開始一個(gè)事務(wù)時(shí),應(yīng)從連接池中獲取一個(gè)連接,并在整個(gè)事務(wù)過程中保持對該連接的使用。例如,在一個(gè)電商訂單處理系統(tǒng)中,當(dāng)用戶下單時(shí),開始一個(gè)事務(wù),從連接池中獲取一個(gè)連接,然后在這個(gè)連接上執(zhí)行插入訂單數(shù)據(jù)、更新庫存等操作。在事務(wù)執(zhí)行過程中,應(yīng)確保所有相關(guān)的數(shù)據(jù)庫操作都在同一個(gè)連接上進(jìn)行,以保證事務(wù)的原子性。
其次,要避免事務(wù)長時(shí)間占用連接而影響整體性能。如果一個(gè)事務(wù)執(zhí)行時(shí)間過長,會導(dǎo)致連接長時(shí)間被占用,其他請求無法及時(shí)獲取到連接,從而影響系統(tǒng)的并發(fā)處理能力。在一個(gè)在線預(yù)訂系統(tǒng)中,如果某個(gè)事務(wù)因?yàn)閺?fù)雜的業(yè)務(wù)邏輯或數(shù)據(jù)庫操作而執(zhí)行了幾分鐘,期間該連接一直被占用,那么在這段時(shí)間內(nèi),其他用戶的預(yù)訂請求可能會因?yàn)闊o法獲取連接而等待,導(dǎo)致系統(tǒng)響應(yīng)變慢,用戶體驗(yàn)下降。為了避免這種情況,可以對事務(wù)的執(zhí)行時(shí)間進(jìn)行監(jiān)控和限制,當(dāng)事務(wù)執(zhí)行時(shí)間超過一定閾值時(shí),及時(shí)進(jìn)行處理,如回滾事務(wù)或優(yōu)化業(yè)務(wù)邏輯,以盡快釋放連接。
此外,還需要注意事務(wù)的隔離級別設(shè)置。不同的隔離級別會對數(shù)據(jù)的一致性和并發(fā)性能產(chǎn)生不同的影響。在高并發(fā)場景下,應(yīng)根據(jù)業(yè)務(wù)需求合理選擇事務(wù)隔離級別。例如,對于一些對數(shù)據(jù)一致性要求較高的業(yè)務(wù),如金融交易、庫存管理等,可以選擇較高的隔離級別,如可串行化隔離級別,以確保數(shù)據(jù)的準(zhǔn)確性;而對于一些對并發(fā)性能要求較高,對數(shù)據(jù)一致性要求相對較低的業(yè)務(wù),如商品瀏覽、統(tǒng)計(jì)分析等,可以選擇較低的隔離級別,如讀已提交隔離級別,以提高系統(tǒng)的并發(fā)處理能力。
六、實(shí)戰(zhàn)案例:看大廠如何做
1. 案例背景與挑戰(zhàn)
某知名互聯(lián)網(wǎng)電商公司,業(yè)務(wù)覆蓋全球多個(gè)地區(qū),擁有數(shù)億用戶。在每年的 “雙 11”“618” 等大型促銷活動期間,平臺會迎來流量的爆發(fā)式增長,并發(fā)請求量瞬間可達(dá)每秒數(shù)十萬甚至數(shù)百萬次。在如此高并發(fā)的業(yè)務(wù)場景下,連接池成為保障系統(tǒng)性能的關(guān)鍵環(huán)節(jié)。
在早期,該公司使用的是一款開源的連接池框架,并采用了默認(rèn)的連接池配置。隨著業(yè)務(wù)的不斷發(fā)展和用戶量的急劇增加,在高并發(fā)場景下,系統(tǒng)逐漸暴露出性能瓶頸和挑戰(zhàn)。連接獲取時(shí)間大幅增加,平均響應(yīng)時(shí)間從正常情況下的幾十毫秒延長到了幾百毫秒甚至秒級,這導(dǎo)致大量用戶在頁面上長時(shí)間等待商品查詢結(jié)果、訂單提交反饋等,用戶體驗(yàn)急劇下降。同時(shí),由于連接池的配置不合理,頻繁的連接創(chuàng)建和銷毀操作使得系統(tǒng)資源消耗巨大,服務(wù)器的 CPU 和內(nèi)存使用率長時(shí)間處于高位,甚至出現(xiàn)了因資源耗盡而導(dǎo)致部分服務(wù)不可用的情況。
2. 優(yōu)化方案與實(shí)施過程
針對這些問題,該公司的技術(shù)團(tuán)隊(duì)進(jìn)行了深入的分析和研究,制定了一系列全面的優(yōu)化方案并付諸實(shí)施。
在參數(shù)調(diào)整方面,技術(shù)團(tuán)隊(duì)對連接池的各項(xiàng)參數(shù)進(jìn)行了細(xì)致的優(yōu)化。他們根據(jù)服務(wù)器的硬件配置和業(yè)務(wù)的并發(fā)特點(diǎn),對最大連接數(shù)、最小空閑連接數(shù)、連接超時(shí)時(shí)間等關(guān)鍵參數(shù)進(jìn)行了重新設(shè)置。經(jīng)過多次性能測試和調(diào)優(yōu),將最大連接數(shù)從原來的 50 調(diào)整為 200,最小空閑連接數(shù)從 10 調(diào)整為 50,連接超時(shí)時(shí)間從 30 秒縮短為 15 秒。這樣的調(diào)整使得連接池能夠更好地適應(yīng)高并發(fā)場景下的業(yè)務(wù)需求,既保證了有足夠的連接可供使用,又避免了過多的空閑連接占用資源。
連接池框架更換也是優(yōu)化的重要舉措之一。技術(shù)團(tuán)隊(duì)經(jīng)過對多種連接池框架的性能測試和對比分析,最終選擇了性能更優(yōu)的 HikariCP 連接池框架替換原來的框架。HikariCP 以其高效的連接管理和低延遲的特性,在高并發(fā)場景下表現(xiàn)出色。在更換框架的過程中,技術(shù)團(tuán)隊(duì)對系統(tǒng)的相關(guān)代碼進(jìn)行了全面的修改和適配,確保新框架能夠與現(xiàn)有系統(tǒng)無縫集成。
在架構(gòu)改進(jìn)方面,為了進(jìn)一步提高系統(tǒng)的性能和穩(wěn)定性,技術(shù)團(tuán)隊(duì)引入了讀寫分離和負(fù)載均衡技術(shù)。他們將數(shù)據(jù)庫的讀操作和寫操作分別分配到不同的數(shù)據(jù)庫實(shí)例上,通過負(fù)載均衡器將并發(fā)請求均勻地分發(fā)到各個(gè)數(shù)據(jù)庫實(shí)例上,從而減輕單個(gè)數(shù)據(jù)庫的壓力。同時(shí),采用了連接池集群技術(shù),將多個(gè)連接池組合成一個(gè)集群,實(shí)現(xiàn)連接資源的共享和動態(tài)分配。在一個(gè)大型促銷活動中,當(dāng)某個(gè)連接池的負(fù)載過高時(shí),請求可以自動被分配到其他負(fù)載較低的連接池中,確保系統(tǒng)能夠穩(wěn)定地處理高并發(fā)請求。
3. 優(yōu)化效果與經(jīng)驗(yàn)總結(jié)
經(jīng)過一系列的優(yōu)化措施實(shí)施后,該電商平臺的系統(tǒng)性能得到了顯著提升。在后續(xù)的 “雙 11” 活動中,系統(tǒng)的平均響應(yīng)時(shí)間從優(yōu)化前的幾百毫秒縮短到了 50 毫秒以內(nèi),吞吐量增加了 3 倍以上,能夠輕松應(yīng)對每秒數(shù)百萬次的并發(fā)請求。用戶在頁面上的操作響應(yīng)迅速,訂單提交成功率大幅提高,極大地提升了用戶體驗(yàn)。
從這個(gè)案例中,我們可以總結(jié)出許多寶貴的經(jīng)驗(yàn)。深入了解業(yè)務(wù)需求和系統(tǒng)性能瓶頸是優(yōu)化的基礎(chǔ),只有準(zhǔn)確把握問題所在,才能制定出針對性的優(yōu)化方案。合理的參數(shù)調(diào)整和優(yōu)秀的連接池框架選擇對于提升連接池性能至關(guān)重要,需要通過充分的性能測試和對比分析來確定最佳配置。此外,架構(gòu)層面的改進(jìn)能夠從根本上提高系統(tǒng)的并發(fā)處理能力和穩(wěn)定性,引入先進(jìn)的技術(shù)和架構(gòu)理念是應(yīng)對高并發(fā)挑戰(zhàn)的有效途徑。
七、連接池需要注意的點(diǎn)
1. 并發(fā)問題
為了使連接管理服務(wù)具有最大的通用性,必須考慮多線程環(huán)境,即并發(fā)問題。這個(gè)問題相對比較好解決,因?yàn)楦鱾€(gè)語言自身提供了對并發(fā)管理的支持像java,c#等等,使用synchronized(java)lock(C#)關(guān)鍵字即可確保線程是同步的。使用方法可以參考,相關(guān)文獻(xiàn)。
2. 事務(wù)處理
我們知道,事務(wù)具有原子性,此時(shí)要求對數(shù)據(jù)庫的操作符合“ALL-OR-NOTHING”原則,即對于一組SQL語句要么全做,要么全不做。
我們知道當(dāng)2個(gè)線程共用一個(gè)連接Connection對象,而且各自都有自己的事務(wù)要處理時(shí)候,對于連接池是一個(gè)很頭疼的問題,因?yàn)榧词笴onnection類提供了相應(yīng)的事務(wù)支持,可是我們?nèi)匀徊荒艽_定那個(gè)數(shù)據(jù)庫操作是對應(yīng)那個(gè)事務(wù)的,這是由于我們有2個(gè)線程都在進(jìn)行事務(wù)操作而引起的。為此我們可以使用每一個(gè)事務(wù)獨(dú)占一個(gè)連接來實(shí)現(xiàn),雖然這種方法有點(diǎn)浪費(fèi)連接池資源但是可以大大降低事務(wù)管理的復(fù)雜性。
3. 連接池的分配與釋放
連接池的分配與釋放,對系統(tǒng)的性能有很大的影響。合理的分配與釋放,可以提高連接的復(fù)用度,從而降低建立新連接的開銷,同時(shí)還可以加快用戶的訪問速度。
對于連接的管理可使用一個(gè)List。即把已經(jīng)創(chuàng)建的連接都放入List中去統(tǒng)一管理。每當(dāng)用戶請求一個(gè)連接時(shí),系統(tǒng)檢查這個(gè)List中有沒有可以分配的連接。如果有就把那個(gè)最合適的連接分配給他(如何能找到最合適的連接文章將在關(guān)鍵議題中指出);如果沒有就拋出一個(gè)異常給用戶,List中連接是否可以被分配由一個(gè)線程來專門管理捎后我會介紹這個(gè)線程的具體實(shí)現(xiàn)。
4. 連接池的配置與維護(hù)
連接池中到底應(yīng)該放置多少連接,才能使系統(tǒng)的性能最佳?系統(tǒng)可采取設(shè)置最小連接數(shù)(minConnection)和最大連接數(shù)(maxConnection)等參數(shù)來控制連接池中的連接。比方說,最小連接數(shù)是系統(tǒng)啟動時(shí)連接池所創(chuàng)建的連接數(shù)。如果創(chuàng)建過多,則系統(tǒng)啟動就慢,但創(chuàng)建后系統(tǒng)的響應(yīng)速度會很快;如果創(chuàng)建過少,則系統(tǒng)啟動的很快,響應(yīng)起來卻慢。這樣,可以在開發(fā)時(shí),設(shè)置較小的最小連接數(shù),開發(fā)起來會快,而在系統(tǒng)實(shí)際使用時(shí)設(shè)置較大的,因?yàn)檫@樣對訪問客戶來說速度會快些。最大連接數(shù)是連接池中允許連接的最大數(shù)目,具體設(shè)置多少,要看系統(tǒng)的訪問量,可通過軟件需求上得到。
如何確保連接池中的最小連接數(shù)呢?有動態(tài)和靜態(tài)兩種策略。動態(tài)即每隔一定時(shí)間就對連接池進(jìn)行檢測,如果發(fā)現(xiàn)連接數(shù)量小于最小連接數(shù),則補(bǔ)充相應(yīng)數(shù)量的新連接,以保證連接池的正常運(yùn)轉(zhuǎn)。靜態(tài)是發(fā)現(xiàn)空閑連接不夠時(shí)再去檢查。