initialSize是什么意思?
initialSize:连接池初始化时初始化的数据库连接数
initialSize在哪个阶段会起作用?
当项目第一次进行增,删,改,查的时候,连接池会初始化,这个时候会根据initialSize参数初始化数据库连接放入连接池中。
画外音:这就是为什么第一次进行数据库操作的时候,响应会比较慢的原因,创建数据库连接是很耗时的,所以初始化连接并不是越多越好
initialSize是怎么起作用的?
当连接池初始化时,会调用DruidDataSource的init初始化数据库连接
public void init() throws SQLException {
if (inited) {
return;
}
//....
}
// init connections
//poolingCount是连接池中已有连接数
while (poolingCount < initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount++] = holder;
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
}
minIdle:连接池中的最小空闲连接数,Druid会定时扫描连接池的连接,如果空闲的连接数大于该值,则关闭多余的连接,反之则创建更多的连接以满足最小连接数要求。
标题为什么要设置这个参数?
设置这个参数可以应对突发流量,如果没有设置空闲连接,当有多个请求同时调用数据库,但是连接池中并没有可用连接,这时就必须创建连接,创建连接是一个非常耗时的操作,有可能会导致请求超时。
minIdle是怎么起作用的?
当连接池初始化时,会初始化一个定时清除空闲连接的任务DestroyTask,该任务默认是1分钟执行一次(使用timeBetweenEvictionRunsMillis参数设置)
protected void createAndStartDestroyThread() {
destroyTask = new DestroyTask();
if (destroyScheduler != null) {
long period = timeBetweenEvictionRunsMillis;
if (period <= 0) {
period = 1000;
}
//定时清除空闲连接的任务,默认1分钟执行一次
destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyTask, period, period,
TimeUnit.MILLISECONDS);
initedLatch.countDown();
return;
}
String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
destroyConnectionThread = new DestroyConnectionThread(threadName);
destroyConnectionThread.start();
}
在这个定时任务中会判断连接池的连接是否满足关闭的条件,如果满足则关闭,满足的条件如下:
空闲时间大于minEvictableIdleTimeMillis(默认30分钟),并且空闲连接数大于minIdle;
空闲时间大于maxEvictableIdleTimeMillis(默认7小时);
//关闭条件,空闲时间大于minEvictableIdleTimeMillis,并且空闲连接大于minIdle,
// 其中checkCount为poolingCount - minIdle,即可能被关闭的连接数量
//或者空闲时间大于maxEvictableIdleTimeMillis
if (idleMillis >= minEvictableIdleTimeMillis) {
if (checkTime && i < checkCount) {
evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
当然也有可能这时数据库中的连接数小于minIdle这个时候就需要创建新的连接了
if (keepAlive && poolingCount + activeCount < minIdle) {
needFill = true;
}
if (needFill) {
lock.lock();
try {
int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
for (int i = 0; i < fillCount; ++i) {
//通知CreateConnectionTask创建连接
emptySignal();
}
} finally {
lock.unlock();
}
总结
minIdle的意义是预防突发流量;
数据源会在DestroyTask定时检查池中的空闲连接,会关闭多余的连接或者创建新的连接。
数据源并不能确保空闲连接一定等于minIdle,可以通过设置timeBetweenEvictionRunsMillis参数调整定时任务的执行间隔,从而控制数据源中空闲连接数更加接近minIdle;
maxActive是什么意思?
maxActive:连接池中的最大连接数,连接池中的连接包含3部分:
activeCount:正在使用的连接;
poolingCount:连接池中的空闲连接;
createTaskCount:正在生成的连接;
这三部分的连接总和不能超过maxActive;
为什么要设置这个参数?
数据库的连接总数是有限制的,有时候僧多粥少,只能限制每个应用的连接数了。
maxActive是怎么起作用的?
maxActive在Druid中有多处使用,最主要的一处是在CreateConnectionTask中
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
clearCreateTask(taskId);
return;
}
maxWait:从连接池中获取连接的最大等待时间,单位ms,默认-1,即会一直等待下去
为什么要设置这个参数?
笔者在使用Druid时都会设置这个参数,这样如果是获取连接超时,更容易从日志中获取调用失败的原因。
如果超时,Druid会抛出以下异常
Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 3000, active 4, maxActive 4, creating 0
at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1722)
at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1402)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5059)
at com.alibaba.druid.filter.logging.LogFilter.dataSource_getConnection(LogFilter.java:886)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
at com.alibaba.druid.filter.FilterAdapter.dataSource_getConnection(FilterAdapter.java:2756)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:680)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1380)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1372)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:109)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:262)
... 11 more
maxWait是怎么起作用的?
在DruidDataSource中的getConnectionInternal方法使用到了maxWait
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
if (closed) {
connectErrorCountUpdater.incrementAndGet(this);
throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
}
}
//此处省略部分代码
if (maxWait > 0) {
//获取连接,如果超时将返回null
holder = pollLast(nanos);
} else {
holder = takeLast();
}
//此处省略部分代码
//如果超时,则holder返回null,然后抛出异常
if (holder == null) {
long waitNanos = waitNanosLocal.get();
StringBuilder buf = new StringBuilder(128);
buf.append("wait millis ")//
.append(waitNanos / (1000 * 1000))//
.append(", active ").append(activeCount)//
.append(", maxActive ").append(maxActive)//
.append(", creating ").append(creatingCount)//
;
if (creatingCount > 0 && createStartNanos > 0) {
long createElapseMillis = (System.nanoTime() - createStartNanos) / (1000 * 1000);
if (createElapseMillis > 0) {
buf.append(", createElapseMillis ").append(createElapseMillis);
}
}
}
总结
maxWait默认是不 m超时,即如果连接池没有空闲连接,则会一直等待下去,但是一般的接口都是有超时时间的,如果接口超时,不方便定位出来是获取不到连接导致的,最好设置maxWait,并且小于接口的超时时间。
timeBetweenEvictionRunsMillis是什么意思?
timeBetweenEvictionRunsMillis默认值是60s,主要作用在两处地方
作为DestroyTask执行的时间周期,DestroyTask主要有两个作用:
判断连接池的连接空闲数是否大于minIdle,如果是则关闭多余的连接,反之则新增连接,具体可以参见
回收连接池泄露的连接,具体可以参见
作为验证连接是否有效的时间周期,如果testOnBorrowfalse并且testWhileIdletrue,则在应用获取连接的时候会判断连接的空闲时间是否大于timeBetweenEvictionRunsMillis,如果大于则会验证该连接是否有效。
public class DestroyConnectionThread extends Thread {、;
public DestroyConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
for (;;) {
// 从前面开始删除
try {
if (closed) {
break;
}
if (timeBetweenEvictionRunsMillis > 0) {
Thread.sleep(timeBetweenEvictionRunsMillis);
} else {
Thread.sleep(1000); //
}
if (Thread.interrupted()) {
break;
}
destroyTask.run();
} catch (InterruptedException e) {
break;
}
}
}
}
总结
timeBetweenEvictionRunsMillis可以用来控制空闲连接数的回收周期
timeBetweenEvictionRunsMillis可以用来控制回收泄露连接的周期
timeBetweenEvictionRunsMillis连接的空闲时间大于该值testWhileIdle才起作用
testWhileIdle是什么意思?
testWhileIdle:如果为true(默认true),当应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用。
testWhileIdle什么时候会起作用?
获取连接时;
testOnBorrowfalse;
testWhileIdletrue;
使用代码在DruidDataSource的getConnectionDirect方法
注意:此时判断连接空闲的依据是空闲时间大于timeBetweenEvictionRunsMillis(默认1分钟),并不是使用minEvictableIdleTimeMillis跟maxEvictableIdleTimeMillis,也就是说如果连接空闲时间超过一分钟就测试一下连接的有效性,但并不是直接剔除;而如果空闲时间超过了minEvictableIdleTimeMillis则会直接剔除。
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
discardConnection(poolableConnection.holder);
continue;
}
} 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;
}
}
}
}
连接池是如何判断连接是否有效的?
判断连接是否可用同testOnBorrow
总结
testWhileIdle的作用跟testOnBorrow是差不多的,都是在获取连接的时候测试连接的有效性,如果两者都为true,则testOnBorrow优先级高,则不会使用到testWhileIdle。
testOnBorrow:如果为true(默认false),当应用向连接池申请连接时,连接池会判断这条连接是否是可用的。
testOnBorrow什么时候会用到?
这个参数主要在DruidDataSource的getConnectionDirect方法中用到
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
if (LOG.isWarnEnabled()) {
LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}
//测试即将返回的连接是否可用
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
discardConnection(poolableConnection.holder);
continue;
}
}
//。。。
}
连接池是如何判断连接是否有效的?
如果是常用的数据库,则使用${DBNAME}ValidConnectionChecker进行判断,比如Mysql数据库,使用MySqlValidConnectionChecker的isValidConnection进行判断;
如果是其他数据库,则使用validationQuery判断;
具体验证规则可查看:Druid配置参数详解-validationQuery
如果验证不通过怎么办?
验证不通过则会直接关闭该连接,并重新从连接池获取下一条连接;
//测试即将返回的连接是否可用
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
//验证不通过则直接剔除该连接,并重新获取下一条连接
discardConnection(poolableConnection.holder);
continue;
}
}
总结
testOnBorrow能够确保我们每次都能获取到可用的连接,但如果设置成true,则每次获取连接的时候都要到数据库验证连接有效性,这在高并发的时候会造成性能下降,可以将testOnBorrow设成false,testWhileIdle设置成true这样能获得比较好的性能。
validationQuery:Druid用来测试连接是否可用的SQL语句,默认值每种数据库都不相同:
Mysql:SELECT 1;
SQLSERVER:SELECT 1;
ORACLE:SELECT ‘x’ FROM DUAL;
PostGresql:SELECT ‘x’;
validationQuery什么时候会起作用?
当Druid遇到testWhileIdle,testOnBorrow,testOnReturn时,就会验证连接的有效性,验证规则如下:
如果有相关数据库的ValidConnectionChecker,则使用ValidConnectionChecker验证(Druid提供常用数据库的ValidConnectionChecker,包括,MySqlValidConnectionChecker,OracleValidConnectionChecker,PGValidConnectionChecker);
如果没有ValidConnectionChecker,则直接使用validationQuery验证;
ValidConnectionChecker是如何验证的?
MySqlValidConnectionChecker会使用Mysql独有的ping方式进行验证,其他数据库其实也都是使用validationQuery进行验证
MySqlValidConnectionChecker验证方式
public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
if (conn.isClosed()) {
return false;
}
//Mysql使用com.mysql.jdbc.MySQLConnection的pingInternal方法进行验证
if (usePingMethod) {
if (conn instanceof DruidPooledConnection) {
conn = ((DruidPooledConnection) conn).getConnection();
}
if (conn instanceof ConnectionProxy) {
conn = ((ConnectionProxy) conn).getRawObject();
}
if (clazz.isAssignableFrom(conn.getClass())) {
if (validationQueryTimeout <= 0) {
validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
}
try {
ping.invoke(conn, true, validationQueryTimeout * 1000);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw e;
}
return true;
}
}
String query = validateQuery;
if (validateQuery == null || validateQuery.isEmpty()) {
query = DEFAULT_VALIDATION_QUERY;
}
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
rs = stmt.executeQuery(query);
return true;
} finally {
JdbcUtils.close(rs);
JdbcUtils.close(stmt);
}
}
总结
不同数据库的默认值不同;
如果是Mysql数据库,则validationQuery不会起作用,Mysql会使用ping的方式验证;
连接池是怎么判断一条连接是Idle状态的?
就是通过这两个参数进行判断的
minEvictableIdleTimeMillis:最小空闲时间,默认30分钟,如果连接池中非运行中的连接数大于minIdle,并且那部分连接的非运行时间大于minEvictableIdleTimeMillis,则连接池会将那部分连接设置成Idle状态并关闭;也就是说如果一条连接30分钟都没有使用到,并且这种连接的数量超过了minIdle,则这些连接就会被关闭了。
maxEvictableIdleTimeMillis:最大空闲时间,默认7小时,如果minIdle设置得比较大,连接池中的空闲连接数一直没有超过minIdle,这时那些空闲连接是不是一直不用关闭?当然不是,如果连接太久没用,数据库也会把它关闭,这时如果连接池不把这条连接关闭,系统就会拿到一条已经被数据库关闭的连接。为了避免这种情况,Druid会判断池中的连接如果非运行时间大于maxEvictableIdleTimeMillis,也会强行把它关闭,而不用判断空闲连接数是否小于minIdle;
这两参数是怎么起作用的?
这两参数是在DestroyTask的shrink方法中用来判断连接是不是应该被关闭的。
//关闭条件,空闲时间大于minEvictableIdleTimeMillis,并且空闲连接大于minIdle,
// 其中checkCount为poolingCount - minIdle,即可能被关闭的连接数量
//或者空闲时间大于maxEvictableIdleTimeMillis
if (idleMillis >= minEvictableIdleTimeMillis) {
if (checkTime && i < checkCount) {
evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
总结
minEvictableIdleTimeMillis连接空闲时间大于该值并且池中空闲连接大于minIdle则关闭该连接
maxEvictableIdleTimeMillis连接空闲时间大于该值,不管minIdle都关闭该连接
removeAbandoned:如果连接泄露,是否需要回收泄露的连接,默认false;
logAbandoned:如果回收了泄露的连接,是否要打印一条log,默认false;
removeAbandonedTimeoutMillis:连接回收的超时时间,默认5分钟;
画外音:这两个参数笔者认为非常重要,但是不知为何,Druid默认是不开启的,并且官方的配置例子中也没有配置这两个参数:Druid官方配置,笔者就因为没有配置这两个参数,曾经吃过大亏。
removeAbandoned,logAbandoned,removeAbandonedTimeoutMillis有什么用?
举个栗子:笔者当初做过单个事务使用多数据源的功能,重写了Spring的ConnectionHolder,由于笔者的疏忽,每次从ConnectionHolder中获取连接时都获取到了两条连接,但是只是使用了其中的一条,相当于另一条连接只是从连接池中拿出来了,但是再也不会还回去了,这样就导致了连接池中的连接很快就消耗光了,即activeCount=maxActive。
如果当时我设置了removeAbandoned就不会出现这个问题,应该Druid会定期检查池中借出去的连接是否处于运行状态,如果不是处于运行状态,并且借出时间超过removeAbandonedTimeoutMillis(默认5分钟)就会回收该连接。
连接是怎么回收的?
Druid每隔timeBetweenEvictionRunsMillis(默认1分钟)会调用DestroyTask,在这里会判断是否可以回收泄露的连接
public class DestroyTask implements Runnable {
public DestroyTask() {
}
@Override
public void run() {
shrink(true, keepAlive);
//判断removeAbandoned是否为true,默认是false,不知为何
if (isRemoveAbandoned()) {
removeAbandoned();
}
}
}
for (; iter.hasNext();) {
DruidPooledConnection pooledConnection = iter.next();
//判断该连接是否还在运行,只回收不运行的连接
//Druid会在连接执行query,update的时候设置为正在运行,
// 并在回收后设置为不运行
if (pooledConnection.isRunning()) {
continue;
}
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
//判断连接借出去的时间大小
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
pooledConnection.setTraceEnable(false);
abandonedList.add(pooledConnection);
}
}
//判断是否要记录连接回收日志,这个很重要,可以及时发现项目中是否有连接泄露
if (isLogAbandoned()) {
StringBuilder buf = new StringBuilder();
buf.append("abandon connection, owner thread: ");
buf.append(pooledConnection.getOwnerThread().getName());
buf.append(", connected at : ");
buf.append(pooledConnection.getConnectedTimeMillis());
buf.append(", open stackTrace\n");
}
总结
笔者认为这三个参数非常重要,但是官方默认是不开启的,建议使用Druid的项目都设置这三个参数,这样可以有效的发现代码中是否有连接泄露。
testOnReturn:如果为true(默认false),当应用使用完连接,连接池回收连接的时候会判断该连接是否还可用。
testOnReturn什么时候会用到?
当连接使用完,调用commit或者rollback方法后,连接池会回收该连接,该参数主要在DruidDataSource的recycle方法中用到
if (testOnReturn) {
boolean validate = testConnectionInternal(holder, physicalConnection);
if (!validate) {
JdbcUtils.close(physicalConnection);
destroyCountUpdater.incrementAndGet(this);
lock.lock();
try {
activeCount--;
closeCount++;
} finally {
lock.unlock();
}
return;
}
}
连接池是如何判断连接是否有效的?
判断连接是否可用同testOnBorrow
Druid配置参数详解-testOnBorrow