数据库连接池大比拼:HikariCP vs Druid,谁更胜一筹?

一、背景介绍

在之前的文章中,我们介绍了 ORM 框架相关的使用方式,这些框架其实都有一个显著的特点,那就是会经常跟数据库打交道。

熟悉 JDBC 的同学可能知道,用 Java 来操作数据库,通常需要先创建一个数据库连接,然后通过这个连接来执行相关的 SQL 语句,当执行完毕之后需要再次手动释放连接。每当来一个涉及数据库的业务操作时,都需要经历同样的操作步骤,示例如下:

复制
// 1.加载数据库驱动包 Class.forName(DRIVER_CLASS); // 2.创建一个数据库连接实例 Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); // 3.执行SQL语句 Statement statement = conn.createStatement(); statement.executeUpdate("insert into tb_user(id, name) values(1, tom) "); .... // 4.关闭连接 statement.close(); conn.close();1.2.3.4.5.6.7.8.9.10.11.

当请求量不高的时候,这种模式不会存在很大的问题,可以正常提供服务,但是当请求量超过 1000 的时候,可能数据库连接数就不够用了,请求创建数据库连接的客户端会直接报错。

为了解决数据库连接数不够用的问题,于是诞生了数据库连接池。其核心思想是:用户需要访问数据库时,并非创建一个新的连接,而是从连接池中取出一个已建立的空闲连接对象,使用它来访问和操作数据库;当用户访问数据库完毕之后,也并非将连接关闭掉,而是将连接对象还回到连接池中,以便下一个请求访问使用,实现连接复用的效果。

事实上,也确实如此,通过连接池来管理数据库的连接,可以有效的提高数据库访问效率,降低连接异常,提升系统响应速度等。

目前,市面上开源的数据库连接池框架非常的多,下面,我们列举几个比较知名的 JDBC 开源连接池组件,简要的了解一下它们的发展历史。

C3P0:一款很古老的 JDBC 连接池,因作为 Hibernate 框架内置的数据库连接池而被开发者所熟知,但是由于性能较差,且代码复杂度很高,官方已经放弃维护DBCP:由 Apache 开发的一个 Java 数据库连接池项目,Tomcat 默认使用的连接池组件,采用单线程来操作连接,性能不好,能支持的并发量低,逐渐被淘汰Tomcat Jdbc Pool:这个数据库连接池可以看作是 DBCP 的升级版,它支持异步方式获取连接,在高并发应用环境下依然保存较好的效果,Tomcat 7及以后默认的连接池组件BoneCP:一款高效、免费的 JDBC  连接池,BoneCP 号称是最快的连接池框架,不过从 2013 年后不再更新,稳定性不佳Druid:阿里出品的一个数据库连接池,功能比较全面,有着高可用且扩展较好的特点,同时还自带监控服务,国内流行度非常高HikariCP:数据库连接池的一个后起之秀,在 BoneCP 基础上开发的一个高性能的 JDBC 连接池,号称性能最好,目前已作为 SpringBoot2 默认的数据库连接池组件

从实际的性能测试来看,排名如下:HikariCP > Druid > tomcat-jdbc > dbcp > c3p0。其中HikariCP的性能最好,这主要得益于它采用最大限度的避免锁竞争的处理思路,进一步加快了连接池的处理效率。

其次,Druid 功能最为全面,比如支持 SQL 拦截、慢 SQL 监控等,同时具有良好的扩展性,性能也不错。

总的来看,如果追求高性能,可以选择 HikariCP 连接池;如果看中更多的功能支持,可以选择 Druid。

下面我们一起来看看这两款连接池的具体应用方式。

二、HicariCP

在此,我们介绍两种方式来完成 HicariCP 连接池的配置初始化,以便于对它的使用有更清晰的理解。

第一种:通过自定义配置文件加载 HicariCP第二种:SpringBoot 整合 HicariCP

2.1、自定义配置文件加载 HicariCP

2.1.1、添加 HicariCP 依赖库

首先在pom.xml文件中,添加 HicariCP 依赖库,内容如下:

复制
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.2.0</version> </dependency>1.2.3.4.5.
2.1.2、编写 HicariCP 相关的配置属性

然后在application.properties文件中,自定义 HicariCP 相关的配置属性,内容如下:

复制
# 自定义 hikari 数据源配置属性 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 11.2.3.4.5.6.7.8.9.10.11.12.13.
2.1.3、编写 HikariDataSource 初始化方法

接着创建一个HikariDataSourceConfig,用于初始化HikariDataSource类并将其注入到 Bean 工厂中,内容如下:

复制
@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(); // 数据源的驱动类型 config.setDriverClassName(driverClassName); // 数据源的连接地址 config.setJdbcUrl(url); // 数据源的用户名 config.setUsername(userName); // 数据源的密码 config.setPassword(password); // 连接池名字 config.setPoolName(poolName); // 最小连接数 config.setMinimumIdle(minimumIdle); // 最大连接数 config.setMaximumPoolSize(maximumPoolSize); // 空闲连接存活最大时间,默认10分钟 config.setIdleTimeout(idleTimeout); // 此属性控制从池中获取的连接的默认自动提交行为,默认值:true config.setAutoCommit(autoCommit); // 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟 config.setMaxLifetime(maxLifetime); // 数据库连接超时时间,默认30秒 config.setConnectionTimeout(connectionTimeout); // 连接测试query config.setConnectionTestQuery(connectionTestQuery); // 初始化 Hikari 连接池 HikariDataSource ds = new HikariDataSource(config); return ds; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.

最后启动服务,即可实现数据源的加载。此方案采用的是通过自定义配置文件完成连接池的手动初始化管理。

2.2、SpringBoot 整合 HicariCP(推荐)

在上文中,我们介绍了通过自定义配置文件来实现HicariCP的加载。其实也可以在 SpringBoot 的自动装配下完成HicariCP的加载。

2.2.1、添加 jdbc 依赖库

如果当前版本是Spring Boot 2.0及以上的版本,HicariCP会作为默认的数据库连接池组件。

当添加spring-boot-starter-jdbc依赖包的时候,会自动添加HicariCP相关的依赖包,无需再次重复添加。

复制
<!-- 添加 jdbc 支持(默认含 HicariCP 依赖包) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>1.2.3.4.5.
2.2.2、添加 HicariCP 相关的配置属性

与上文不同,本次我们需要采用 Spring Boot 能识别的属性配置,以便帮助自动完成HicariCP数据源的初始化。

复制
# 添加hikari数据源配置 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 11.2.3.4.5.6.7.8.9.10.11.12.13.14.

最后启动服务,即可实现数据源的加载。这种实现方式与上文介绍的方式效果一样,并且配置更加简单。

三、Druid

Druid 作为一个开源数据库连接池组件,因其强大的监控功能,在国内应用也非常广泛。

在此,我们也介绍两种方式来完成 Druid 连接池的配置初始化,以便于对它的使用有更清晰的理解。

第一种:通过自定义配置文件加载 Druid第二种:SpringBoot 整合 Druid

3.1、自定义配置文件加载 Druid

3.1.1、添加 Druid 依赖库

首先在pom.xml文件中,添加 Druid 依赖库,内容如下:

复制
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.17</version> </dependency>1.2.3.4.5.
3.1.2、编写 Druid 相关的配置属性

然后在application.properties文件中,自定义 Druid 相关的配置属性,内容如下:

复制
# 添加druid数据源配置 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=true1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.
3.1.3、编写 DruidDataSource 初始化方法

接着创建一个DruidConfig,用于初始化DruidDataSource类并将其注入到 Bean 工厂中,内容如下:

复制
@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(); // 数据源的驱动类型 datasource.setDriverClassName(driverClassName); // 数据源的连接地址 datasource.setUrl(url); // 数据源的用户名 datasource.setUsername(username); // 数据源的密码 datasource.setPassword(password); // 初始化连接池大小 datasource.setInitialSize(initialSize); // 设置最小连接数 datasource.setMinIdle(minIdle); // 设置最大连接数 datasource.setMaxActive(maxActive); // 设置获取连接时的最大等待时间 datasource.setMaxWait(maxWait); // 一个连接在池中最小生存的时间,单位是毫秒 datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); // 多久才进行一次检测需要关闭的空闲连接,单位是毫秒 datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 检测连接是否有效的 SQL语句 datasource.setValidationQuery(validationQuery); // 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能 datasource.setTestWhileIdle(testWhileIdle); // 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能 datasource.setTestOnBorrow(testOnBorrow); // 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能 datasource.setTestOnReturn(testOnReturn); // 是否打开PSCache,oracle支持,Mysql不支持 datasource.setPoolPreparedStatements(poolPreparedStatements); // druid监控配置信息 try { // 配置监控统计拦截的filters,去掉后监控界面sql无法统计,wall用于防火墙 datasource.setFilters(filters); } catch (SQLException e) { LOGGER.error("druid configuration initialization filter", e); } // 通过connectProperties属性来打开mergeSql功能;慢SQL记录 datasource.setConnectionProperties(connectionProperties); // 合并多个DruidDataSource的监控数据 datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat); return datasource; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.106.107.108.
3.1.4、编写监控服务初始化方法

在上文我们有说到,Druid 自带强大的监控服务,通过相关配置类即可将其开启,内容如下:

复制
@Configuration publicclass DruidMonitorConfig { /** * 这里相当于servlet的web.xml * @return */ @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet() { ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet()); // 添加映射 bean.addUrlMappings("/druid/*"); //设置一些初始化参数 Map<String, String> initParas = new HashMap<>(); initParas.put("loginUsername", "admin"); initParas.put("loginPassword", "123456"); //允许谁能防伪 initParas.put("allow", "");//这个值为空或没有就允许所有人访问,ip白名单 //initParas.put("allow","localhost");//只允许本机访问,多个ip用逗号,隔开 //initParas.put("deny","");//ip黑名单,拒绝谁访问 deny和allow同时存在优先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<>(); //可以设置也可以获取,设置一个阿里巴巴的过滤器 bean.setFilter(new WebStatFilter()); bean.addUrlPatterns("/*"); //可以过滤和排除哪些东西 Map<String, String> initParams = new HashMap<>(); //把不需要监控的过滤掉,这些不进行统计 initParams.put("exclusions", "*.js,*.css,/druid/*"); bean.setInitParameters(initParams); return bean; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.

最后启动服务,即可实现数据源的加载。

同时,在浏览器访问http://127.0.0.1:8080/druid/页面,输入在DruidMonitorConfig配置类中的账号、密码,即可登陆监控服务,查询相关 SQL 监控看板,部分界面如下:

图片

3.2、SpringBoot 整合 Druid(推荐)

如果觉得以上配置很麻烦,也可以通过 SpringBoot 的自动装配下完成Druid的加载。

3.2.1、添加 Druid-starter 依赖库

首先添加druid-spring-boot-starter依赖包,通过它来完成配置参数自动装配,内容如下:

复制
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency>1.2.3.4.5.
3.2.2、添加 Druid 相关的配置属性

本次我们需要采用 Spring Boot 能识别的属性配置,以便帮助自动完成Druid数据源的初始化。

复制
# 添加druid数据源配置 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 # 以下是配置监控信息(可选) 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/*1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.

最后启动服务,即可实现数据源的加载。效果等于通过自定义配置文件实现手动加载的结果。

四、小结

本文主要围绕 Spring Boot 整合数据库连接池组件,实现系统连接数的可控管理目标进行一次知识内容的整理和总结!

五、参考

1.https://zhuanlan.zhihu.com/p/460846041

2.https://developer.aliyun.com/article/1000769

THE END