日常开发中,数据库连接池是个必不可少的配置,使用优秀的数据库连接池,可以有效的提高数据库访问效
率,降低连接异常等,本篇就来学习一下Spirngboot自带连接池和阿里Druid两个最常见的连接池
什么是HikariCP
HikariCP是由日本程序员开源的一个数据库连接池组件,代码非常轻量,并且速度非常的快。根据官方提供的数据,在i7,开启32个线程32个连接的情况下,进行随机数据库读写操作,HikariCP的速度是现在常用的C3P0数据库连接池的数百倍。在SpringBoot2.0中,官方默认也是使用的HikariCP作为数据库连接池,可见HikariCP连接池的目的就是为了极致的数据库连接性能体验,下面附上一张HikariCP和其他连接池的比较图:
从图中的结果可以看出来,HikariCP从性能来说的确一骑绝尘,那么HikariCP是如何做到这么极致的性能呢?主要依托于HikariCP自身所做的优化机制
HikariCP优化机制
字节码精简
HikariCP优化了代码,尽量减少了生成的字节码,使得cpu可以加载更多程序代码
优化了拦截和代理机制
HikariCP对拦截器机制和代理机制进行了代码优化处理,例如Statement proxy只有100行代码,大大减少了代码量,只有其他连接池例如BoneCP的十分之一
自定义数组
HikariCP针对数组操作进行了自定义数组--FastStatementList,用来替代jdk的ArrayList,优化了get、remove等方法,避免了每次调用get的时候进行范围检查,也避免了每次remove操作的时候会将数据从头到尾进行扫描的性能问题
自定义集合
同样的,针对jdk自带的集合类,HikariCP专门封装了无锁的集合类型 ,用来提高在使用中的读写并发的效率,减少并发造成的资源竞争问题
CPU时间片算法优化
HikariCP也对cpu时间片分配算法进行了优化,尽可能使得一个时间片内完成相关的操作
使用HikariCP
了解了HikariCP以后,我们开始使用吧,首先找到HikariCP的坐标:
com.zaxxer
HikariCP
2.7.6
然后配置HikariCP对应的配置文件,用来读取/加载连接池配置:
/**
* HikariCP连接池配置
*/
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.url}")
private String dataSourceUrl;
@Value("${spring.datasource.username}")
private String user;
@Value("${spring.datasource.password}")
private String password;
@Bean
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dataSourceUrl); //数据源url
config.setUsername(user); //用户名
config.setPassword(password); //密码
config.addDataSourceProperty("cachePrepStmts", "true"); //是否自定义配置,为true时下面两个参数才生效
config.addDataSourceProperty("prepStmtCacheSize", "250"); //连接池大小默认25,官方推荐250-500
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); //单条语句最大长度默认256,官方推荐2048
config.addDataSourceProperty("useServerPrepStmts", "true"); //新版本MySQL支持服务器端准备,开启能够得到显著性能提升
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("useLocalTransactionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
HikariDataSource ds = new HikariDataSource(config);
return ds;
}
}
接着我们在SpringBoot的application.properties 文件中进行配置:
server.port=8090
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-active=15
spring.datasource.max-lifetime=86430000
spring.datasource.log-abandoned=true
spring.datasource.remove-abandoned=true
spring.datasource.remove-abandoned-timeout=60
spring.datasource.initialize=false
spring.datasource.sqlScriptEncoding=UTF-8
配置完毕,此时我们启动工程,即可看到控制台已经将我们配置的HikariCP数据库连接池信息打印出来了
阿里巴巴Druid
提到大名鼎鼎的Druid连接池,相信很多人都不陌生,因为该连接池是阿里开源的优秀的连接池,几乎已经成为现在使用最多的连接池之一。我们先打开Druid的官方github:
https://github.com/alibaba/druid
可以看到此项目已经有19.4k的star数,并且是2019最受欢迎的开源之一,经历过真实线上双十一的考验,可以说是个很成熟的开源连接池,而Druid连接池专为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Logging能诊断Hack应用行为等。
快速使用
Druid 0.1.18 之后版本都发布到maven中央仓库中,所以你只需要在项目的pom.xml中加上dependency就可以了,中央仓库坐标如下:
com.alibaba
druid
${druid-version}
这里我们使用了springBoot,由于默认支持的数据连接池只有四种:dbcp,dbcp2, tomcat, hikariCP,并不包含druid,所以我们这里也可以选择直接使用阿里官方编写的druid-spring-boot-starter,并且我们添加对应的mybatis和pageHelper的依赖:
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
com.github.pagehelper
pagehelper-spring-boot-starter
1.1.1
com.alibaba
druid-spring-boot-starter
1.1.1
在application.properties中 进行数据源配置:
# 数据库访问配置
# 主数据源,默认的
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=123456
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
#spring.datasource.useGlobalDataSourceStat=true
配置完毕以后,由于这里我们使用了mybatis,所以我们还要在application.properties中配置一下mybatis相关:
#mybatis
#entity扫描的包名
mybatis.type-aliases-package=com.xiaolyuh.domain.model
#Mapper.xml所在的位置
mybatis.mapper-locations=classpath*:/mybaits/*Mapper.xml
#开启MyBatis的二级缓存
mybatis.configuration.cache-enabled=true
#pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
准备就绪后,我们来编写一个测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DataSourceTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void testDataSource() throws Exception {
// 获取配置的数据源
DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println(dataSource.getClass().getName());
}
}
将测试方法运行起来,即可在控制台中看到对应的数据源的输出信息
Druid开启监控统计功能
druid最强大的功能就是自身提供了对sql的数据监控功能,并且内置了很多详细的拦截器,可以实现多个角度的拦截处理,那么如何开启监控?在Druid中内置提供了一个StatViewServlet用于展示Druid的统计信息,这个StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
如果是ssm工程,则可以在web.xml中配置StatViewServlet,如下:
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
DruidStatView
/druid/*
配置完毕以后,启动工程则可以按照配置的监控地址访问监控信息,默认为:http://ip:port/project-name/druid/index.html
同样的,在StatViewServlet中我们可以添加访问密码的设置,只需要配置Servlet的 loginUsername
和 loginPassword
这两个初始参数 即可,例如:
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
resetEnable
true
loginUsername
druid
loginPassword
druid
DruidStatView
/druid/*
此时访问监控页面的时候就需要输入我们设置的用户名和密码了,如果还想针对用户有敏感信息配置和访问权限控制,我们还可以配置allow
和deny
参数,例如:
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
allow
128.242.127.1/24,128.242.128.1
deny
128.242.127.4
这里的访问规则为:
1.deny配置优先于allow,即deny为优先拒绝,即使在allow中配置了白名单,但是只要存在于deny中,一样也会被拒绝访问
2.如果allow没有配置,或者配置为空,默认为全部都可以访问,不进行白名单限制
使用SpringBoot配置监控
由于我们这里使用的是SpringBoot,所以我们仅需要在application.properties
添加配置统计拦截的filters:
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall,log4j
这里的配置是通过别名方式配置扩展支持的插件,如下:
- 监控统计用的filter:stat
- 日志用的filter:log4j
- 防御sql注入的filter:wall
接着我们需要在application.properties
继续添加WebStatFilter
和StatViewServlet
的配置项:
# WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
#启动项目后访问 http://127.0.0.1:8080/druid
#是否启用StatFilter默认值true
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
#缺省sessionStatMaxCount是1000个
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#关闭session统计功能
spring.datasource.druid.web-stat-filter.session-stat-enable=false
#配置principalSessionName,使得druid能够知道当前的session的用户是谁
#如果你session中保存的是非string类型的对象,需要重载toString方法
spring.datasource.druid.web-stat-filter.principalSessionName=xxx.user
#如果user信息保存在cookie中,你可以配置principalCookieName,使得druid知道当前的user是谁
spring.datasource.druid.web-stat-filter.principalCookieName=xxx.user
#druid 0.2.7版本开始支持profile,配置profileEnable能够监控单个url调用的sql列表。
spring.datasource.druid.web-stat-filter.profile-enable=false
# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
#启动项目后访问 http://127.0.0.1:8080/druid
#是否启用StatViewServlet默认值true
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.urlPattern=/druid/*
#禁用HTML页面上的“Reset All”功能
spring.datasource.druid.stat-view-servlet.resetEnable=false
#用户名
spring.datasource.druid.stat-view-servlet.loginUsername=admin
#密码
spring.datasource.druid.stat-view-servlet.loginPassword=admin
#IP白名单(没有配置或者为空,则允许所有访问)
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1
#IP黑名单 (存在共同时,deny优先于allow)
spring.datasource.druid.stat-view-servlet.deny=192.168.1.73
接着我们启动工程,访问http://localhost/druid ,输入配置的用户名:admin以及密码:admin,即可看到druid的监控页面:
慢sql日志打印
在开发过程中,往往会遇到sql时间过长问题,为了定位慢sql,我们往往会定义固定时长作为慢sql的时长,而Druid支持慢sql查询,在Druid中内置提供了一个StatFilter,用于统计监控信息 ,我们可以利用这个StatFilter来统计慢sql:
StatFilter的别名是stat,这个别名映射配置信息保存在druid-xxx.jar!/META-INF/druid-filter.properties
我们需要在Spring中加入以下配置:
当然如果需要我们可以同时开启多个Filter进行组合使用,中间用,隔开即可,如下:
而如果开启慢sql的记录,我们需要先定义slowSqlMillis 来配置sql慢查询的标准,如下:
配置完毕以后,所有的超过10s的sql都会在监控页面的慢sql模块记录,可以查看具体的sql以及执行时间等,快速定位开发过程中的慢sql
DruidDataSource配置
如果我们根据业务的不同,需要更改不同的配置,这个时候我们就需要参考DriudDataSource的配置,通用的配置项如下:
配置 | 缺省值 | 描述 |
---|---|---|
name | 如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this) | |
url | 连接数据库的url | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码 | |
driverClassName | 根据url自动识别 | 可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经弃用 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxPoolPreparedStatementPerConnectionSize | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 | |
validationQueryTimeout | 单位:秒,检测连接是否有效的超时时间。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 |
keepAlive | false | 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作 |
timeBetweenEvictionRunsMillis | 60s | 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
numTestsPerEvictionRun | 30分钟 | 已经弃用 |
minEvictableIdleTimeMillis | 连接保持空闲而不被驱逐的最小时间 | |
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall | |
proxyFilters | 类型是List |
Druid常见配置与问题
除了我们已经了解的druid常见知识以外,开发中经常还会遇到很多其他常见需求,如开启druid的防sql注入功能、记录每次执行的sql、数据库加密、log输出执行的sql等常见需求,这个时候我们就可以在官方的github的文档中查找,官方已经给我们整理好了一些开发常见的问题,地址如下:
https://github.com/alibaba/druid/wiki/常见问题
总结
在实际开发过程中,我们往往会根据自身需求或者项目本身来选择最适合的连接池,这里我们将常见的数据连接池从可扩展性、可靠稳定性、性能、可运维性以及自身功能几个方向进行了比较,可供参考: