前言
druid是阿里爸爸的開源數據庫連接池,據說其性能算是位於領先的水平,從連接的創建和銷燬這個性能方面優於其它連接池,但是覺得和HikariCP,的速度比起來還是差點。但是兩者各有好處,一個是擴展性比較優秀功能比較全,一個是速度比較塊。以下是性能對照圖:
圖片出處:
https://github.com/brettwooldridge/HikariCP 圖片出處:
druid Github
圖片出處:
http://blog.didispace.com/java-datasource-pool-compare/
於是便引起了我這個菜鳥的好奇心,由於druid功能強大,我是菜鳥,就看看連接池的一些內容吧。
小試牛刀-使用
配置:
配置列表,其兼容DBCP的各項配置,但是有些許的語義區別。以下是我簡單的入門配置。
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14_customer
username=root
password=
#初始鏈接數,在連接池被創建的時候初始化的連接數
initSize=20
#最大連接池數量
maxActive=20
#最小連接池數量
minIdle=5
#超時等待時間
maxWait=60000
#指定連接屬性
connectionProperties=useSSL=true;rewriteBatchedStatements=true
在java中簡單使用:
package Utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class Druid {
private static DataSource dataSource;
static {
try {
InputStream inputStream = DBCP.class.getClassLoader().getResourceAsStream("dbconfig.properties");
Properties properties = new Properties();
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void release(Connection conn, Statement st, ResultSet rs) {
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
挺簡單的沒啥好說的
看看源碼
重要變量
DruidAbstractDataSource.class
//重入鎖
protected ReentrantLock lock;
//empty,notEmpty條件,負責線程的阻塞喚醒
protected Condition notEmpty;
protected Condition empty;
DruidSource.class
//存放線程的底層數組
private volatile DruidConnectionHolder[] connections;
工廠函數創建datasource
public static DataSource createDataSource(Map properties) throws Exception {
DruidDataSource dataSource = new DruidDataSource();
//從properties中配置datasource
config(dataSource, properties);
return dataSource;
}
在獲取連接的過程進行初始化
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
this.init();
if (this.filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return this.getConnectionDirect(maxWaitMillis);
}
}
初始化的方法中有這樣的幾個線程啓動,一個是create一個是destroy
this.createAndLogThread();
this.createAndStartCreatorThread();
this.createAndStartDestroyThread();
lock、condition、兩個線程-創建/銷燬connection
從源碼我們可以看出druid連接池是基於ReetrantLock的,它有兩個condition,empty和notEmpty,一個是監控連接池是否爲空一個是監控連接池不爲空的。這是連接池的核心部分,讓我們一探究竟。注意,這兩個條件condition都是誰用?等待爲空的線程是創建condition的線程,等待不爲空的是getConnection。
創建線程:
com.alibaba.druid.pool.DruidDataSource.CreateConnectionThread
try {
//等待爲空的變量,依據這個變量來進行condition:empty的阻塞(awiait)
boolean emptyWait = true;
if (DruidDataSource.this.createError != null && DruidDataSource.this.poolingCount == 0 && !discardChanged) {
emptyWait = false;
}
if (emptyWait && DruidDataSource.this.asyncInit && DruidDataSource.this.createCount < (long)DruidDataSource.this.initialSize) {
emptyWait = false;
}
if (emptyWait) {
if (DruidDataSource.this.poolingCount >= DruidDataSource.this.notEmptyWaitThreadCount && (!DruidDataSource.this.keepAlive || DruidDataSource.this.activeCount + DruidDataSource.this.poolingCount >= DruidDataSource.this.minIdle)) {
DruidDataSource.this.empty.await();
}
if (DruidDataSource.this.activeCount + DruidDataSource.this.poolingCount >= DruidDataSource.this.maxActive) {
DruidDataSource.this.empty.await();
continue;
}
}
等待空的兩個if條件
- 池中的連接數量>=等待不爲空的線程數量.意思就是說你請求一個連接我連接池裏面有,這個時候就不用創建連接了,連接的線程就await,阻塞
- 活躍的數量+池中的數量>=連接池總數量,意思就是說連接池數量達到了池的上限,這個時候就不能再創建連接了。
if (connection != null) {
// 往線程中put進一個連接connection
boolean result = DruidDataSource.this.put(connection);
if (!result) {
JdbcUtils.close(connection.getPhysicalConnection());
DruidDataSource.LOG.info("put physical connection to pool failed.");
}
errorCount = 0;
}
獲取線程
獲取線程的邏輯主要是它來實現的,讓我們進一步探究一下。
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
this.init();
if (this.filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return this.getConnectionDirect(maxWaitMillis);
}
}
最後的彈出連接是這個方法:
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
try {
while(this.poolingCount == 0) {
//連接池空了,喚醒生產者來創建連接
this.emptySignal();
if (this.failFast && this.failContinuous.get()) {
throw new DataSourceNotAvailableException(this.createError);
}
++this.notEmptyWaitThreadCount;
if (this.notEmptyWaitThreadCount > this.notEmptyWaitThreadPeak) {
this.notEmptyWaitThreadPeak = this.notEmptyWaitThreadCount;
}
//阻塞了一個,數量也就-1
try {
this.notEmpty.await();
} finally {
--this.notEmptyWaitThreadCount;
}
++this.notEmptyWaitCount;
if (!this.enable) {
connectErrorCountUpdater.incrementAndGet(this);
throw new DataSourceDisableException();
}
}
} catch (InterruptedException var5) {
this.notEmpty.signal();
++this.notEmptySignalCount;
throw var5;
}
//減少池中的連接計數,下面的部分就是取連接的操作
this.decrementPoolingCount();
DruidConnectionHolder last = this.connections[this.poolingCount];
this.connections[this.poolingCount] = null;
return last;
}
兩個有用的字段:notEmptyWaitThreadCount這個字段也就是消費者數量,notEmptyWaitCount維護的就是正在等待的消費者數量
這讓我就突然想起了生產者和消費者的思想,創建線程是生產者,獲取線程是消費者,不難發現notEmpty這個Condition是用在了它上面。
銷燬線程
public class DestroyTask implements Runnable {
public DestroyTask() {
}
public void run() {
DruidDataSource.this.shrink(true, DruidDataSource.this.keepAlive);
if (DruidDataSource.this.isRemoveAbandoned()) {
DruidDataSource.this.removeAbandoned();
}
}
}
shrink的關鍵就在這,其主要功能是收縮就是空閒的太多了要進行回收了。
if (evictCount > 0) {
for(checkCount = 0; checkCount < evictCount; ++checkCount) {
holer = this.evictConnections[checkCount];
connection = holer.getConnection();
JdbcUtils.close(connection);
destroyCountUpdater.incrementAndGet(this);
}
Arrays.fill(this.evictConnections, (Object)null);
}
總結
這次的小blog只是就簡單的實現原理看了看,可以看出連接池是生產者消費者這種形式運作的,主要是兩個線程來保證連接池,其中很多細節未涉及,筆者能力有限。如果有機會還想學習一下監控,據說是通過責任鏈來實現的,責任鏈我不懂,就沒有深入。總的來說,旨在入個門把。
本來想自己寫一個源碼分析的,馬上下班了,先轉一個,有時間在自己寫
轉:https://blog.csdn.net/qq_41376740/article/details/81869261