數(shù)據(jù)庫連接池大比拼:HikariCP vs Druid,誰更勝一籌?
一、背景介紹
在之前的文章中,我們介紹了 ORM 框架相關(guān)的使用方式,這些框架其實(shí)都有一個顯著的特點(diǎn),那就是會經(jīng)常跟數(shù)據(jù)庫打交道。
熟悉 JDBC 的同學(xué)可能知道,用 Java 來操作數(shù)據(jù)庫,通常需要先創(chuàng)建一個數(shù)據(jù)庫連接,然后通過這個連接來執(zhí)行相關(guān)的 SQL 語句,當(dāng)執(zhí)行完畢之后需要再次手動釋放連接。每當(dāng)來一個涉及數(shù)據(jù)庫的業(yè)務(wù)操作時,都需要經(jīng)歷同樣的操作步驟,示例如下:
// 1.加載數(shù)據(jù)庫驅(qū)動包
Class.forName(DRIVER_CLASS);
// 2.創(chuàng)建一個數(shù)據(jù)庫連接實(shí)例
Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
// 3.執(zhí)行SQL語句
Statement statement = conn.createStatement();
statement.executeUpdate("insert into tb_user(id, name) values(1, 'tom') ");
....
// 4.關(guān)閉連接
statement.close();
conn.close();
當(dāng)請求量不高的時候,這種模式不會存在很大的問題,可以正常提供服務(wù),但是當(dāng)請求量超過 1000 的時候,可能數(shù)據(jù)庫連接數(shù)就不夠用了,請求創(chuàng)建數(shù)據(jù)庫連接的客戶端會直接報(bào)錯。
為了解決數(shù)據(jù)庫連接數(shù)不夠用的問題,于是誕生了數(shù)據(jù)庫連接池。其核心思想是:用戶需要訪問數(shù)據(jù)庫時,并非創(chuàng)建一個新的連接,而是從連接池中取出一個已建立的空閑連接對象,使用它來訪問和操作數(shù)據(jù)庫;當(dāng)用戶訪問數(shù)據(jù)庫完畢之后,也并非將連接關(guān)閉掉,而是將連接對象還回到連接池中,以便下一個請求訪問使用,實(shí)現(xiàn)連接復(fù)用的效果。
事實(shí)上,也確實(shí)如此,通過連接池來管理數(shù)據(jù)庫的連接,可以有效的提高數(shù)據(jù)庫訪問效率,降低連接異常,提升系統(tǒng)響應(yīng)速度等。
目前,市面上開源的數(shù)據(jù)庫連接池框架非常的多,下面,我們列舉幾個比較知名的 JDBC 開源連接池組件,簡要的了解一下它們的發(fā)展歷史。
- C3P0:一款很古老的 JDBC 連接池,因作為 Hibernate 框架內(nèi)置的數(shù)據(jù)庫連接池而被開發(fā)者所熟知,但是由于性能較差,且代碼復(fù)雜度很高,官方已經(jīng)放棄維護(hù)
- DBCP:由 Apache 開發(fā)的一個 Java 數(shù)據(jù)庫連接池項(xiàng)目,Tomcat 默認(rèn)使用的連接池組件,采用單線程來操作連接,性能不好,能支持的并發(fā)量低,逐漸被淘汰
- Tomcat Jdbc Pool:這個數(shù)據(jù)庫連接池可以看作是 DBCP 的升級版,它支持異步方式獲取連接,在高并發(fā)應(yīng)用環(huán)境下依然保存較好的效果,Tomcat 7及以后默認(rèn)的連接池組件
- BoneCP:一款高效、免費(fèi)的 JDBC 連接池,BoneCP 號稱是最快的連接池框架,不過從 2013 年后不再更新,穩(wěn)定性不佳
- Druid:阿里出品的一個數(shù)據(jù)庫連接池,功能比較全面,有著高可用且擴(kuò)展較好的特點(diǎn),同時還自帶監(jiān)控服務(wù),國內(nèi)流行度非常高
- HikariCP:數(shù)據(jù)庫連接池的一個后起之秀,在 BoneCP 基礎(chǔ)上開發(fā)的一個高性能的 JDBC 連接池,號稱性能最好,目前已作為 SpringBoot2 默認(rèn)的數(shù)據(jù)庫連接池組件
從實(shí)際的性能測試來看,排名如下:HikariCP > Druid > tomcat-jdbc > dbcp > c3p0。其中HikariCP的性能最好,這主要得益于它采用最大限度的避免鎖競爭的處理思路,進(jìn)一步加快了連接池的處理效率。
其次,Druid 功能最為全面,比如支持 SQL 攔截、慢 SQL 監(jiān)控等,同時具有良好的擴(kuò)展性,性能也不錯。
總的來看,如果追求高性能,可以選擇 HikariCP 連接池;如果看中更多的功能支持,可以選擇 Druid。
下面我們一起來看看這兩款連接池的具體應(yīng)用方式。
二、HicariCP
在此,我們介紹兩種方式來完成 HicariCP 連接池的配置初始化,以便于對它的使用有更清晰的理解。
- 第一種:通過自定義配置文件加載 HicariCP
- 第二種:SpringBoot 整合 HicariCP
2.1、自定義配置文件加載 HicariCP
2.1.1、添加 HicariCP 依賴庫
首先在pom.xml文件中,添加 HicariCP 依賴庫,內(nèi)容如下:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
2.1.2、編寫 HicariCP 相關(guān)的配置屬性
然后在application.properties文件中,自定義 HicariCP 相關(guān)的配置屬性,內(nèi)容如下:
# 自定義 hikari 數(shù)據(jù)源配置屬性
hikari.driver-class-name=com.mysql.jdbc.Driver
hikari.url=jdbc:mysql://localhost:3306/test
hikari.username=root
hikari.password=root
hikari.pool-name=HikariCP
hikari.minimum-idle=5
hikari.maximum-pool-size=20
hikari.idle-timeout=600000
hikari.auto-commit=true
hikari.max-lifetime=1800000
hikari.connection-timeout=30000
hikari.connection-test-query=SELECT 1
2.1.3、編寫 HikariDataSource 初始化方法
接著創(chuàng)建一個HikariDataSourceConfig,用于初始化HikariDataSource類并將其注入到 Bean 工廠中,內(nèi)容如下:
@Configuration
publicclass HikariDataSourceConfig {
@Value("${hikari.driver-class-name}")
private String driverClassName;
@Value("${hikari.url}")
private String url;
@Value("${hikari.username}")
private String userName;
@Value("${hikari.password}")
private String password;
@Value("${hikari.pool-name}")
private String poolName;
@Value("${hikari.minimum-idle}")
private Integer minimumIdle;
@Value("${hikari.maximum-pool-size}")
private Integer maximumPoolSize;
@Value("${hikari.idle-timeout}")
private Integer idleTimeout;
@Value("${hikari.auto-commit}")
private Boolean autoCommit;
@Value("${hikari.max-lifetime}")
private Integer maxLifetime;
@Value("${hikari.connection-timeout}")
private Integer connectionTimeout;
@Value("${hikari.connection-test-query}")
private String connectionTestQuery;
@Bean
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
// 數(shù)據(jù)源的驅(qū)動類型
config.setDriverClassName(driverClassName);
// 數(shù)據(jù)源的連接地址
config.setJdbcUrl(url);
// 數(shù)據(jù)源的用戶名
config.setUsername(userName);
// 數(shù)據(jù)源的密碼
config.setPassword(password);
// 連接池名字
config.setPoolName(poolName);
// 最小連接數(shù)
config.setMinimumIdle(minimumIdle);
// 最大連接數(shù)
config.setMaximumPoolSize(maximumPoolSize);
// 空閑連接存活最大時間,默認(rèn)10分鐘
config.setIdleTimeout(idleTimeout);
// 此屬性控制從池中獲取的連接的默認(rèn)自動提交行為,默認(rèn)值:true
config.setAutoCommit(autoCommit);
// 此屬性控制池中連接的最長生命周期,值0表示無限生命周期,默認(rèn)30分鐘
config.setMaxLifetime(maxLifetime);
// 數(shù)據(jù)庫連接超時時間,默認(rèn)30秒
config.setConnectionTimeout(connectionTimeout);
// 連接測試query
config.setConnectionTestQuery(connectionTestQuery);
// 初始化 Hikari 連接池
HikariDataSource ds = new HikariDataSource(config);
return ds;
}
}
最后啟動服務(wù),即可實(shí)現(xiàn)數(shù)據(jù)源的加載。此方案采用的是通過自定義配置文件完成連接池的手動初始化管理。
2.2、SpringBoot 整合 HicariCP(推薦)
在上文中,我們介紹了通過自定義配置文件來實(shí)現(xiàn)HicariCP的加載。其實(shí)也可以在 SpringBoot 的自動裝配下完成HicariCP的加載。
2.2.1、添加 jdbc 依賴庫
如果當(dāng)前版本是Spring Boot 2.0及以上的版本,HicariCP會作為默認(rèn)的數(shù)據(jù)庫連接池組件。
當(dāng)添加spring-boot-starter-jdbc依賴包的時候,會自動添加HicariCP相關(guān)的依賴包,無需再次重復(fù)添加。
<!-- 添加 jdbc 支持(默認(rèn)含 HicariCP 依賴包) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
2.2.2、添加 HicariCP 相關(guān)的配置屬性
與上文不同,本次我們需要采用 Spring Boot 能識別的屬性配置,以便幫助自動完成HicariCP數(shù)據(jù)源的初始化。
# 添加hikari數(shù)據(jù)源配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
最后啟動服務(wù),即可實(shí)現(xiàn)數(shù)據(jù)源的加載。這種實(shí)現(xiàn)方式與上文介紹的方式效果一樣,并且配置更加簡單。
三、Druid
Druid 作為一個開源數(shù)據(jù)庫連接池組件,因其強(qiáng)大的監(jiān)控功能,在國內(nèi)應(yīng)用也非常廣泛。
在此,我們也介紹兩種方式來完成 Druid 連接池的配置初始化,以便于對它的使用有更清晰的理解。
- 第一種:通過自定義配置文件加載 Druid
- 第二種:SpringBoot 整合 Druid
3.1、自定義配置文件加載 Druid
3.1.1、添加 Druid 依賴庫
首先在pom.xml文件中,添加 Druid 依賴庫,內(nèi)容如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
3.1.2、編寫 Druid 相關(guān)的配置屬性
然后在application.properties文件中,自定義 Druid 相關(guān)的配置屬性,內(nèi)容如下:
# 添加druid數(shù)據(jù)源配置
druid.driver-class-name=com.mysql.jdbc.Driver
druid.url=jdbc:mysql://localhost:3306/test
druid.username=root
druid.password=root
druid.initialSize=5
druid.minIdle=5
druid.maxActive=20
druid.maxWait=60000
druid.minEvictableIdleTimeMillis=300000
druid.timeBetweenEvictionRunsMillis=60000
druid.validationQuery=SELECT 1 FROM DUAL
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=false
druid.filters=stat,wall
druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
druid.useGlobalDataSourceStat=true
3.1.3、編寫 DruidDataSource 初始化方法
接著創(chuàng)建一個DruidConfig,用于初始化DruidDataSource類并將其注入到 Bean 工廠中,內(nèi)容如下:
@Configuration
publicclass DruidConfig {
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(DruidConfig.class);
@Value("${druid.driver-class-name}")
private String driverClassName;
@Value("${druid.url}")
private String url;
@Value("${druid.username}")
private String username;
@Value("${druid.password}")
private String password;
@Value("${druid.initialSize}")
private Integer initialSize;
@Value("${druid.minIdle}")
private Integer minIdle;
@Value("${druid.maxActive}")
private Integer maxActive;
@Value("${druid.maxWait}")
private Integer maxWait;
@Value("${druid.minEvictableIdleTimeMillis}")
private Integer minEvictableIdleTimeMillis;
@Value("${druid.timeBetweenEvictionRunsMillis}")
private Integer timeBetweenEvictionRunsMillis;
@Value("${druid.validationQuery}")
private String validationQuery;
@Value("${druid.testWhileIdle}")
privateboolean testWhileIdle;
@Value("${druid.testOnBorrow}")
privateboolean testOnBorrow;
@Value("${druid.testOnReturn}")
privateboolean testOnReturn;
@Value("${druid.poolPreparedStatements}")
privateboolean poolPreparedStatements;
@Value("${druid.filters}")
private String filters;
@Value("${druid.connectionProperties}")
private String connectionProperties;
@Value("${druid.useGlobalDataSourceStat}")
privateboolean useGlobalDataSourceStat;
@Bean
public DruidDataSource dataSourceDefault(){
DruidDataSource datasource = new DruidDataSource();
// 數(shù)據(jù)源的驅(qū)動類型
datasource.setDriverClassName(driverClassName);
// 數(shù)據(jù)源的連接地址
datasource.setUrl(url);
// 數(shù)據(jù)源的用戶名
datasource.setUsername(username);
// 數(shù)據(jù)源的密碼
datasource.setPassword(password);
// 初始化連接池大小
datasource.setInitialSize(initialSize);
// 設(shè)置最小連接數(shù)
datasource.setMinIdle(minIdle);
// 設(shè)置最大連接數(shù)
datasource.setMaxActive(maxActive);
// 設(shè)置獲取連接時的最大等待時間
datasource.setMaxWait(maxWait);
// 一個連接在池中最小生存的時間,單位是毫秒
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 多久才進(jìn)行一次檢測需要關(guān)閉的空閑連接,單位是毫秒
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 檢測連接是否有效的 SQL語句
datasource.setValidationQuery(validationQuery);
// 申請連接時如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效,默認(rèn)false,建議開啟,不影響性能
datasource.setTestWhileIdle(testWhileIdle);
// 申請連接時執(zhí)行validationQuery檢測連接是否有效,默認(rèn)true,開啟后會降低性能
datasource.setTestOnBorrow(testOnBorrow);
// 歸還連接時執(zhí)行validationQuery檢測連接是否有效,默認(rèn)false,開啟后會降低性能
datasource.setTestOnReturn(testOnReturn);
// 是否打開PSCache,oracle支持,Mysql不支持
datasource.setPoolPreparedStatements(poolPreparedStatements);
// druid監(jiān)控配置信息
try {
// 配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻
datasource.setFilters(filters);
} catch (SQLException e) {
LOGGER.error("druid configuration initialization filter", e);
}
// 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
datasource.setConnectionProperties(connectionProperties);
// 合并多個DruidDataSource的監(jiān)控?cái)?shù)據(jù)
datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
return datasource;
}
}
3.1.4、編寫監(jiān)控服務(wù)初始化方法
在上文我們有說到,Druid 自帶強(qiáng)大的監(jiān)控服務(wù),通過相關(guān)配置類即可將其開啟,內(nèi)容如下:
@Configuration
publicclass DruidMonitorConfig {
/**
* 這里相當(dāng)于servlet的web.xml
* @return
*/
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet());
// 添加映射
bean.addUrlMappings("/druid/*");
//設(shè)置一些初始化參數(shù)
Map<String, String> initParas = new HashMap<>();
initParas.put("loginUsername", "admin");
initParas.put("loginPassword", "123456");
//允許誰能防偽
initParas.put("allow", "");//這個值為空或沒有就允許所有人訪問,ip白名單
//initParas.put("allow","localhost");//只允許本機(jī)訪問,多個ip用逗號,隔開
//initParas.put("deny","");//ip黑名單,拒絕誰訪問 deny和allow同時存在優(yōu)先deny
initParas.put("resetEnable", "false");//禁用HTML頁面的Reset按鈕
bean.setInitParameters(initParas);
return bean;
}
/**
* 配置一個過濾器,Servlet按上面的方式注冊Filter也可以這樣
* @return
*/
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
//可以設(shè)置也可以獲取,設(shè)置一個阿里巴巴的過濾器
bean.setFilter(new WebStatFilter());
bean.addUrlPatterns("/*");
//可以過濾和排除哪些東西
Map<String, String> initParams = new HashMap<>();
//把不需要監(jiān)控的過濾掉,這些不進(jìn)行統(tǒng)計(jì)
initParams.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
return bean;
}
}
最后啟動服務(wù),即可實(shí)現(xiàn)數(shù)據(jù)源的加載。
同時,在瀏覽器訪問http://127.0.0.1:8080/druid/頁面,輸入在DruidMonitorConfig配置類中的賬號、密碼,即可登陸監(jiān)控服務(wù),查詢相關(guān) SQL 監(jiān)控看板,部分界面如下:
圖片
3.2、SpringBoot 整合 Druid(推薦)
如果覺得以上配置很麻煩,也可以通過 SpringBoot 的自動裝配下完成Druid的加載。
3.2.1、添加 Druid-starter 依賴庫
首先添加druid-spring-boot-starter依賴包,通過它來完成配置參數(shù)自動裝配,內(nèi)容如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
3.2.2、添加 Druid 相關(guān)的配置屬性
本次我們需要采用 Spring Boot 能識別的屬性配置,以便幫助自動完成Druid數(shù)據(jù)源的初始化。
# 添加druid數(shù)據(jù)源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=20
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.pool-prepared-statements=false
# 以下是配置監(jiān)控信息(可選)
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.merge-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=5000
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=12345678
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusinotallow=*.js,*.css,/druid/*
最后啟動服務(wù),即可實(shí)現(xiàn)數(shù)據(jù)源的加載。效果等于通過自定義配置文件實(shí)現(xiàn)手動加載的結(jié)果。
四、小結(jié)
本文主要圍繞 Spring Boot 整合數(shù)據(jù)庫連接池組件,實(shí)現(xiàn)系統(tǒng)連接數(shù)的可控管理目標(biāo)進(jìn)行一次知識內(nèi)容的整理和總結(jié)!
五、參考
1.https://zhuanlan.zhihu.com/p/460846041
2.https://developer.aliyun.com/article/1000769