本來不打算寫這個題目的,因爲 Druid
大多都是在 Spring
中使用的,它很多功能非常強大,但是對於 MySQL
性能測試中並不實用。但是由於特殊原因,還是得把這個拾起來。
在以前的性能測試的過程當中,我通常會採用 線程綁定連接
的方式進行測試,畢竟也用不到很多線程,再不濟我就用 common-pool2
自己寫一個。但是考慮到穩定性測試當中,持續時間非常久,自定義的功能缺少自愈能力,最終還是選擇了使用已有成熟的 MySQL
連接池工具,經過幾番對比,最後選擇了 Druid
。
Druid簡介
Druid連接池是阿里巴巴開源的數據庫連接池項目,爲監控而生,內置強大的監控功能,且監控特性不影響性能。Druid連接池功能強大,性能優越,使用佔比高,是一款優秀的數據庫連接池。
Druid連接池的主要特點包括:
- 高性能: Druid連接池採用了一系列性能優化策略,包括預先創建連接、連接池複用、有效的連接驗證等,以提供高效的數據庫連接獲取和釋放操作。
- 可靠性: Druid連接池提供了多種故障處理機制,可以有效地應對各種異常情況,確保數據庫連接的可靠性。
- 可管理性: Druid連接池提供了豐富的監控和統計功能,可以實時監控連接池的狀態、活動連接數、請求頻率、SQL執行情況等,方便用戶進行管理和優化。
- 安全性: Druid連接池內置了防火牆功能,可以有效地防止SQL注入攻擊,並提供審計功能,可以幫助用戶追蹤數據庫操作行爲。
- 擴展性: Druid連接池支持多種數據庫類型,並可以方便地擴展支持新的數據庫類型。
Druid連接池的使用非常簡單,只需幾行代碼即可配置和使用,是Java應用開發中不可多得的利器。
一個例子
static void main(String[] args) {
// 創建 Druid 數據源
DruidDataSource dataSource = new DruidDataSource()
// 配置數據庫連接信息
dataSource.setUrl("jdbc:mysql://localhost:3306/funtester")
dataSource.setUsername("root")
dataSource.setPassword("funtester")
// 獲取數據庫連接
Connection connection = dataSource.getConnection()
// 執行 SQL 語句
Statement statement = connection.createStatement()
def query = statement.executeQuery("select id, uid, create_time from record order by id desc limit 10;")
while (query.next()) {
println("id: ${query.getInt(1)}, uid: ${query.getInt(2)}, create_time: ${query.getTimestamp(3)}")
}
query.close()
// 關閉數據庫連接
statement.close()
connection.close()
}
控制檯打印信息就不再展示了。
Druid配置項
上面例子中我們採取先創建 com.alibaba.druid.pool.DruidDataSource
對象,然後進行配置項設置。我們還有一種語法,如下:
// 配置Druid連接池屬性
Properties properties = new Properties()
properties.put("driverClassName", "com.mysql.cj.jdbc.Driver")
properties.put("url", "jdbc:mysql://localhost:3306/funtester")
properties.put("username", "root")
properties.put("password", "funtester")
properties.put("maxActive", "2")
// 創建Druid連接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties)
Druid連接池提供了非常豐富的配置參數,可以根據實際需求進行定製化配置,下面列出了一些常用的配置項:
- 基本配置:
driverClassName
: 數據庫驅動類名url
: 數據庫URL連接字符串username
: 數據庫用戶名password
: 數據庫密碼
- 初始化配置:
initialSize
: 初始化連接池時創建的連接數量,默認0maxActive
: 連接池中可同時連接的最大的活動的連接數,默認8maxIdle
: 連接池中最大的空閒的連接數,太大may會使系統稍慢,若有批量執行查詢請增大該值,默認8
- 超時時間配置:
maxWait
: 獲取連接時最大等待時間(毫秒),超過則拋出異常,小於0則無限等待,默認無限timeBetweenEvictionRunsMillis
: 對象被過期前的休息時間,用於檢測連接是否被佔用,避免因其他原因長時間佔用而不能被檢測並從而移除廢棄連接。默認1分鐘minEvictableIdleTimeMillis
: 連接在池中最小生存的時間,單位是毫秒,默認30分鐘
- 測試配置:
testWhileIdle
: 是否在從連接池取連接時檢測連接有效性,默認false,非常耗時testOnBorrow
: 是否在連接池中取出連接前進行檢測連接有效性,默認true,建議設置爲false,性能更好testOnReturn
: 是否在連接池中歸還連接時檢測連接有效性,默認false
- 空閒連接回收配置:
removeAbandoned
: 是否允許連接池中連接被回收,默認falseremoveAbandonedTimeout
: 應該回收過期連接的時間,單位爲秒,默認300logAbandoned
: 是否按指定時間輸出連接回收的記錄,默認false
- 其他配置:
filters
: 配置一些擴展插件,常用的有stat(計算一些統計數據)、log4j(使用log4j記錄連接池日誌)、wall(用於防止SQL注入)等validationQuery
: 用來檢測連接是否有效的sql,這個會在應用程序每次申請連接時執行,類似select 1
accessToUnderlyingConnectionAllowed
: 是否允許訪問底層連接,true則允許用戶獲取到物理連接,默認false
以上是一些Druid連接池常用的配置參數,在配置時可以根據項目實際情況進行調整。比如對於長時間保持空閒狀態的應用可以將maxIdle
設置小一些,而對於併發量大的應用則需要將maxActive
設置大一些。配置合理的連接池參數有利於提升應用的性能和穩定性。
併發
在性能測試過程中少不了要對連接池併發獲取連接、歸還連接。下面是演示的例子:
import com.alibaba.druid.pool.DruidDataSource
import com.alibaba.druid.pool.DruidDataSourceFactory
import com.funtester.frame.SourceCode
import java.sql.Connection
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class DruidConcurrencyDemo extends SourceCode {
private static DruidDataSource dataSource
static {
try {
// 配置Druid連接池屬性
Properties properties = new Properties()
properties.put(DruidDataSourceFactory.PROP_DRIVERCLASSNAME, "com.mysql.cj.jdbc.Driver")
properties.put(DruidDataSourceFactory.PROP_URL, "jdbc:mysql://localhost:3306/funtester")
properties.put(DruidDataSourceFactory.PROP_USERNAME, "root")
properties.put(DruidDataSourceFactory.PROP_PASSWORD, "funtester")
properties.put(DruidDataSourceFactory.PROP_MAXACTIVE, "10")
properties.put(DruidDataSourceFactory.PROP_INITIALSIZE, "3")
properties.put(DruidDataSourceFactory.PROP_MAXWAIT, "5000")
// 創建Druid連接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties)
} catch (Exception e) {
e.printStackTrace()
}
}
static void main(String[] args) throws InterruptedException {
int threadCount = 4 // 模擬4個併發請求
CountDownLatch latch = new CountDownLatch(threadCount)
ExecutorService executorService = Executors.newFixedThreadPool(threadCount)
// 併發獲取連接
for (int i = 0; i < threadCount; i++) {
executorService.execute(() -> {
try {
Connection connection = dataSource.getConnection()
// 模擬一些操作
Thread.sleep(1000) // 模擬1秒的操作時間
output("活躍連接數: " + dataSource.getActiveCount())
output("空閒連接數: " + dataSource.getPoolingCount())
connection.close() // 關閉連接
} catch (Exception e) {
e.printStackTrace()
} finally {
latch.countDown() // 計數器減一
}
})
}
latch.await() // 等待所有線程執行完畢
executorService.shutdown() // 關閉線程池
// 獲取連接池狀態
output("活躍連接數: " + dataSource.getActiveCount())
output("空閒連接數: " + dataSource.getPoolingCount())
}
}
控制檯輸出:
16:31:55:819 pool-2-thread-3 {dataSource-1} inited
16:31:57:353 pool-2-thread-1 活躍連接數: 2
16:31:57:353 pool-2-thread-2 活躍連接數: 2
16:31:57:354 pool-2-thread-1 空閒連接數: 0
16:31:57:354 pool-2-thread-2 空閒連接數: 0
16:31:58:365 pool-2-thread-3 活躍連接數: 2
16:31:58:365 pool-2-thread-4 活躍連接數: 2
16:31:58:365 pool-2-thread-3 空閒連接數: 0
16:31:58:366 pool-2-thread-4 空閒連接數: 0
16:31:58:369 main 活躍連接數: 0
16:31:58:370 main 空閒連接數: 2
如果你在設置中增加了 com.alibaba.druid.pool.DruidDataSourceFactory#PROP_MAXIDLE = "maxIdle";
的話,控制檯會提示:
main maxIdle is deprecated
該配置已經過期了。