資料庫連線(2) - 為什麼C3P0連線池那麼慢
摘要
承接上篇資料庫連線(1)從jdbc到mybatis,介紹下資料庫連線池技術
為什麼需要連線池
在上一篇中我們介紹說客戶端建立一次連線耗時太長(建立連線,設定字符集,autocommit等),如果在每個sql操作都需要經歷建立連線,關閉連線。不僅應用程式響應慢,而且會產生很多臨時物件,應用伺服器GC壓力大。另外資料庫server端對連線也有限制,比如MySQL預設151個連線(實際環境中一般會調大這個值,尤其是多個服務時)
現在面臨的問題就是如何提高對稀缺性的資源高效管理。因為客戶端與資料庫的連線本質就是tcp請求,加上基於tcp協議封裝的mysql請求。那麼通常解決這類問題,我們有兩種方式,一種是池話技術,即使用一個容器,提前建立好連線,請求時直接從池子裡面拿,另外一種就是利用IO多路複用技術。利於在spring5中,mongo ,cassandra等資料庫的訪問就可以利用reactive來實現了,但是關係型資料庫不行,原因在於關型資料庫的訪問目前都是基於JDBC,JDBC運算元據庫的流程,建立connection,構建Statement,執行這一套是序列的,阻塞型。一個事務中的多個操作只能在同一個連線中完成。所以不能使用IO多路複用技術,是受限於JDBC的阻塞。對於其他語言,是可以的,比如nodejs
所以我們使用池話技術來提供資料庫訪問
資料庫連線池與執行緒池的區別
通常,程式設計師在業務開發中經常使用的是執行緒池,利用CPU多核,來併發處理任務,提高效率。資料庫連線池與執行緒池同屬於池化技術,沒有太大區別,都是需要管理池的大小,資源控制。不同的資料庫連線池中放的是connection,同時還需要管理事務,所以通常資料庫連線池中會對這個進行優化
從連線池中取連線執行sql操作,多了兩步設定connection autocommit屬性操作
通過將connection分成兩組,來提供效率
開源連線池技術介紹
一個基本的資料庫連線池包括幾大部分
-
取出連線
-
放回連線
-
非同步/同步處理執行緒
進行建立連線和銷燬連線
對於一個資料庫連線池的根本就在於併發容器的實現,也是決定連線池的效率高低,常見的連線池配置如下
initialSize:初始連線數
maxActive: 最大連線數量
minIdle: 最小連線數量
maxWait: 獲取連線最大等待時間ms
minEvictableIdleTimeMillis:連線保持空閒而不被驅逐的最小時間
timeBetweenEvictionRunsMillis:銷燬執行緒的時間檢測
testOnBorrow:申請連線時執行,比較影響效能
validationQuery:testOnBorrow為true檢測是否是有效連線sql
testWhileIdle:申請連線的時候檢測
目前的開源資料庫連線池主要有以下,
C3P0,和DBCP是出現的比較早的資料庫連線,主要用於hibernate,和tomcat6.0以下,比較穩定,在低併發的情況下,工作還可以,但是高併發下,效能比較差,所以在tomcat6,又重寫了一個jdbc-pool,來替代DBCP。
Druid是阿里巴巴開源的高效能資料庫連線池,目前基本是各大網際網路公司的標配了,加上又是國內的,文件比較易讀,所以流行度比較高,另外一個是hikariCP,效能比較高,目前普及度還不是特別高。
那為什麼C3P0和DBCP的效能比較低呢?
前面提到資料庫連線池本質上就是一個併發容器的實現。通常我們可以利用List+鎖機制實現。或者使用jdk原生的,比如CopyOnWriteList這樣的結構
而鎖通過有兩種,一種JVM級別的synchronized,一種是JDK提供的ReentrantLock,兩者在語義上並沒有多大區別,互斥,記憶體可見,可重入。JDK5中引入ReentrantLock時,效能比synchronzied要好很多,而在JDK6中,經過優化後的,兩者並無太大效能上區別。所以ReentrantLock更多優勢在於
-
可以中斷等待的執行緒
一直拿不到鎖的等待執行緒,可以中斷掉,避免出現死鎖 -
可以結合Condition,更加靈活控制執行緒
看下com.mchange.v2.c3p0.DriverManagerDataSource 的實現
// should NOT be sync'ed -- driver() is sync'ed and that's enough
// sync'ing the method creates the danger that one freeze on connect
// blocks access to the entire DataSource
public Connection getConnection() throws SQLException
{
ensureDriverLoaded();
// 通過此方法來獲取連線
Connection out = driver().connect( jdbcUrl, properties );
if (out == null)
throw new SQLException("Apparently, jdbc URL '" + jdbcUrl + "' is not valid for the underlying " +
"driver [" + driver() + "].");
return out;
}
在獲取連線的時候首先在一個synchonized中去獲取java.sql.Driver,
private synchronized Driver driver() throws SQLException
{
//To simulate an unreliable DataSource...
//double d = Math.random() * 10;
//if ( d > 1 )
// throw new SQLException(this.getClass().getName() + " TEST of unreliable Connection. If you're not testing, you shouldn't be seeing this!");
//System.err.println( "driver() <-- " + this );
if (driver == null)
{
if (driverClass != null && forceUseNamedDriverClass)
{
if ( Debug.DEBUG && logger.isLoggable( MLevel.FINER ) )
logger.finer( "Circumventing DriverManager and instantiating driver class '" + driverClass +
"' directly. (forceUseNamedDriverClass = " + forceUseNamedDriverClass + ")" );
try
{
driver = (Driver) Class.forName( driverClass ).newInstance();
this.setDriverClassLoaded( true );
}
catch (Exception e)
{ SqlUtils.toSQLException("Cannot instantiate specified JDBC driver. Exception while initializing named, forced-to-use driver class'" + driverClass +"'", e); }
}
else
driver = DriverManager.getDriver( jdbcUrl );
}
return driver;
}
具體的連線池管理是BasicResourcePool,可以看下程式碼,裡面全都是synchronized方法。併發效能怎麼能好。
再來看下Druid的實現,DruidDataSource
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
DruidConnectionHolder holder;
for (boolean createDirect = false;;) {
// 帶有超時的連線獲取
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
}
併發環境下去拿連線時,並沒有在讀操作上加鎖,比互斥鎖的效能要高
互斥鎖是一種比較保守的策略,像synchronized,它避免了寫寫衝突,寫讀衝突,和讀讀衝突,對於資料庫連線池,應用程式來拿,是一個讀操作比較多的,允許多個讀同時操作,能夠提高系統的併發性。
private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException {
long estimate = nanos;
// 佇列阻塞,當取連線時,沒有連線,執行緒空轉,等待另外建立執行緒去建立連線
for (;;) {
if (poolingCount == 0) {
// 通知建立執行緒去建立連線
emptySignal();
}
}
decrementPoolingCount();
// 從陣列中獲取連線
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
long waitNanos = nanos - estimate;
last.setLastNotEmptyWaitNanos(waitNanos);
return last;
}
在建立連線執行緒,銷燬連線執行緒中增加寫鎖
private boolean put(DruidConnectionHolder holder) {
// 加鎖
lock.lock();
try {
if (poolingCount >= maxActive) {
return false;
}
connections[poolingCount] = holder;
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
//發出連線池非空訊號,等待的執行緒開始處理
notEmpty.signal();
notEmptySignalCount++;
if (createScheduler != null) {
createTaskCount--;
if (poolingCount + createTaskCount < notEmptyWaitThreadCount //
&& activeCount + poolingCount + createTaskCount < maxActive) {
emptySignal();
}
}
} finally {
lock.unlock();
}
return true;
}
關注【方丈的寺院】,與方丈一起開始技術修行之路
HikariCP在讀寫鎖的基礎上進行了進一步的優化
https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole
參考
相關文章
- 何為資料庫連線池?其工作原理是什麼?資料庫
- c3p0資料庫連線池問題資料庫
- Java技術分享:什麼是資料庫連線池?Java資料庫
- 資料庫連線池的問題,連線池物件為靜態變數有問題麼資料庫物件變數
- 資料庫連線池資料庫
- 《四 資料庫連線池原始碼》手寫資料庫連線池資料庫原始碼
- 資料庫連線池-Druid資料庫連線池原始碼解析資料庫UI原始碼
- 資料庫連線非常慢資料庫
- 資料庫連線緩慢資料庫
- 4、資料庫連線池的概念及C3P0、Uruid兩種連線池的使用資料庫UI
- 為什麼我的 PHP 資料庫連線失敗?PHP資料庫
- iis網站怎麼連線資料庫連線網站資料庫
- 資料庫連線池原理資料庫
- Proxool資料庫連線池資料庫
- JAVA資料庫連線池Java資料庫
- Flask資料庫連線池Flask資料庫
- Java中的資料庫連線池:HikariCP與C3P0Java資料庫
- Druid資料庫連線池就這麼簡單UI資料庫
- 【MySQL】自定義資料庫連線池和開源資料庫連線池的使用MySql資料庫
- mysql資料庫怎麼連線MySql資料庫
- python資料庫連線池Python資料庫
- 手寫資料庫連線池資料庫
- 瞭解資料庫連線池資料庫
- 資料庫連線池的理解資料庫
- 資料庫連線池的使用資料庫
- 資料庫連線池淺析資料庫
- WASCE的資料庫連線池資料庫
- JNDI配置資料庫連線池資料庫
- PROXOOL資料庫連線池使用資料庫
- 關於資料庫連線池資料庫
- .net 資料庫連線池配置資料庫
- 資料庫連線池實現資料庫
- Javaweb-資料庫連線池JavaWeb資料庫
- 資料庫連線池優化配置(druid,dbcp,c3p0)資料庫優化UI
- 利用weblogic的POOL(連線池)連線資料庫 (轉)Web資料庫
- setup中為何資料庫連線不上那資料庫
- mysql資料庫連線失敗是什麼原因MySql資料庫
- python怎麼連線資料庫Python資料庫