1. 背景
DRUID是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池。
2. 介绍
(1)DataSource中URL重要配置参数(Spring配置前缀:spring.datasource.url)
配置 | 缺省值 | 说明 | 推荐说明 |
serverTimezone | 时区 | Asia/Shanghai | |
useUnicode | true | ||
characterEncoding | 1、存数据 数据库在存放项目数据的时候会先用UTF-8格式将数据解码成字节码,然后再将解码后的字节码重新使用GBK编码,并存放到数据库中。 2、取数据 在数据库中取数据的时候,数据库会先将数据库中的数据按GBK格式解码成字节码,然后再将解码后的字节码重新按UTF-8格式编码数据,最后再将数据返回给客户端 | UTF8 | |
useSSL | true | MySQL在高版本需要指明是否进行SSL连接 | false |
(2)Druid重要配置参数(Spring配置前缀:spring.datasource.druid)
配置 | 缺省值 | 描述 | 推荐说明 |
initial-size | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 | 连接数=(核心数 * 2) + 有效磁盘数) |
min-idle | 最小连接池数量 | 与initialSize一致 | |
max-idle | 8 | 最大连接池数量 | |
max-wait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁 | ||
use-unfair-lock | false | 如果设置了maxWait,则启用公平锁 | |
time-between-eviction-runs-millis | 60000 | 在空闲连接回收器线程运行期间休眠时间,它决定线程多久验证空闲连接或丢弃连接的频率 | 源码上看,是通过validation-query配置的语句语句检测连接的有效性,如果连接失败了则丢弃 |
min-evictableIdle-time-millis | 1800000 | 连接在池中保持空闲而不被回收的最小时间 | |
validation-query | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用false | 如果检测到已知驱动后,会使用MySqlValidConnectionChecker类进行有效检测,走的是ping策略,而不是该配置,哪怕没有配置也会走默认的 “select 1”,前提是使用MySqlValidConnectionChecker,不然是直接返回true的 | |
validation-query-timeout | -1 | 检测连接超时时间 | 虽然这里默认是-1,但是实际代码会设置成1s的时间 |
test-on-borrow | false | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 | |
test-while-idle | true | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 | |
test-on-return | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 | |
pool-prepared-statements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭 | |
max-open-prepared-statements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 | |
filter-class-names | |||
flter.stat.slow-sql-millis |
3. 源码
4. 实战
4.1 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
4.2 Druid配置
以数据库实例是4核8G为例(待考量):
spring:
datasource:
druid:
initial-size: 10
min-idle: 10
max-active: 25
max-wait: 60000
use-unfair-lock: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
5. FAQ
5.1 Druid是否有必要在设置了maxWait后,还要强制设置useUnfairLock=true?
设置后,性能会翻倍(见参考资料有赞优化),但是这样做有没有其他问题?
5.2 检测到连接达空闲时间上线时,Druid会物理关闭该连接,那么它不保证最小连接数吗?会重新开启新的连接吗?
(1) com.alibaba.druid.pool.DruidDataSource#getConnectionDirect
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// ...
if (testOnBorrow) {
// ...
} else {
if (poolableConnection.conn.isClosed()) {
discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
continue;
}
if (testWhileIdle) {
final DruidConnectionHolder holder = poolableConnection.holder;
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = holder.lastActiveTimeMillis;
long lastExecTimeMillis = holder.lastExecTimeMillis;
long lastKeepTimeMillis = holder.lastKeepTimeMillis;
if (checkExecuteTime
&& lastExecTimeMillis != lastActiveTimeMillis) {
lastActiveTimeMillis = lastExecTimeMillis;
}
if (lastKeepTimeMillis > lastActiveTimeMillis) {
lastActiveTimeMillis = lastKeepTimeMillis;
}
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (timeBetweenEvictionRunsMillis <= 0) {
timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
}
// 如果空闲时间达到阙值
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0 // unexcepted branch
) {
// 检测连接是否有效
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
// 无效则关闭连接
discardConnection(poolableConnection.holder);
continue;
}
}
}
}
// ...
}
(2) 关闭连接后,如果当前存活数小于最小连接数
空信号的处理利用的是AQS的Condition.sign(),这里还不确定会不会开启。Condition忘记了~
public void discardConnection(DruidConnectionHolder holder) {
if (holder == null) {
return;
}
Connection conn = holder.getConnection();
if (conn != null) {
JdbcUtils.close(conn);
}
lock.lock();
try {
if (holder.discard) {
return;
}
if (holder.active) {
activeCount--;
holder.active = false;
}
discardCount++;
holder.discard = true;
if (activeCount <= minIdle) {
emptySignal();
}
} finally {
lock.unlock();
}
}