Druid⽆效链接回收策略(源码分析)(mysql8⼩时连接失效问题)
⽬录
mysql下载链接问题背景(异常Communications link failure)
最近添加了数据库监控后发现会有⼏⼗万分之⼀概率查询失败. 查看⽇志发现异常如下 :
Caused by: ptions.jdbc4.CommunicationsException: Communications link failure
监控如下:
连接池使⽤的dbcp 1.4版本. 查了⼀下同学们讲是mysql 连接如果8⼩时内持续空闲会被关闭.
通过mysql > show variables like '%timeout%';查到结果确实如此
随即按照⼤家讲的将dbcp更换成了druid (1.1.2) 连接池.问题得到解决.另外修改mysql设置也可以好像, 由于集团弹性数据库修改起来流程⽐较繁琐. 没有深究
druid数据库连接池关键配置说明(注意标红配置)
initialSize: 初始化连接个数
maxActive: 最⼤连接池数量
minIdle: 最⼩连接池数量
validationQuery: ⽤来检测连接是否有效的sql,要求是⼀个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作⽤。
testOnBorrow: false 申请连接时不执⾏validationQuery检测
testOnReturn: false 归还连接时不执⾏validationQuery检测
testWhileIdle: true 申请连接的时候检测,如果空闲时间⼤于timeBetweenEvictionRunsMillis,执⾏validationQuery检测
:timeBetweenEvictionRunsMillis 有两个含义:
2.Destroy线程会检测连接的间隔时间 对应以下第⼆种⽅式
minEvictableIdleTimeMillis: Destroy worker执⾏时判断连接空闲时间是否⼤于 minEvictableIdleTimeM
illis, 如果⼤于判断线程池中空闲连接数是否⼤于minIdle, 如果⼤于回收此连接
maxEvictableIdleTimeMillis: Destroy worker执⾏时判断连接空闲时间是否⼤于 maxEvictableIdleTimeMillis, 如果⼤于回收此连接(忽略minIdle).
druid数据库连接池超时连接回收源码分析
第⼀种⽅式 : 获取连接时校验
public DruidPooledConnection getConnection()throws SQLException {//获取连接⽅法
Connection(this.maxWait);
}
public DruidPooledConnection getConnection(long maxWaitMillis)throws SQLException {
this.init();
if(this.filters.size()>0){
FilterChainImpl filterChain =new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
}else{
ConnectionDirect(maxWaitMillis);
}
}
public DruidPooledConnection getConnectionDirect(long maxWaitMillis)throws SQLException {
int notFullTimeoutRetryCnt =0;
DruidPooledConnection poolableConnection;
while(true){//注意这⾥的循环.直到获取到或者抛出或者break; while循环才会出
while(true){
try{
poolableConnection =ConnectionInternal(maxWaitMillis);//获取
break;
}catch(GetConnectionTimeoutException var23){
if(notFullTimeoutRetryCnt &FullTimeoutRetryCount ||this.isFull()){
throw var23;
}
++notFullTimeoutRetryCnt;
if(LOG.isWarnEnabled()){
LOG.warn("get connection timeout retry : "+ notFullTimeoutRetryCnt);
}
}
}
stOnBorrow){
//如果testOnBorrow=true,每次获取连接时都会检查连接有效性.效率较差
boolean validate =stConnectionInternal(poolableConnection.holder, );
if(validate){
break;
}
if(LOG.isDebugEnabled()){
LOG.debug("skip not validate connection.");
LOG.debug("skip not validate connection.");
}
/
/销毁连接(检查有效性结果:⽆效)
this.discardConnection(poolableConnection.holder);
}else isClosed()){
//如果连接已经关闭,销毁
this.discardConnection(poolableConnection.holder);
}else{
//如果testWhileIdle=false,break;不执⾏⼀下校验,连接被返回
if(!stWhileIdle){
break;
}
DruidConnectionHolder holder = poolableConnection.holder;
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = holder.lastActiveTimeMillis;
long lastExecTimeMillis = holder.lastExecTimeMillis;
long lastKeepTimeMillis = holder.lastKeepTimeMillis;
if(this.checkExecuteTime && lastExecTimeMillis != lastActiveTimeMillis){
lastActiveTimeMillis = lastExecTimeMillis;
}
if(lastKeepTimeMillis > lastActiveTimeMillis){
lastActiveTimeMillis = lastKeepTimeMillis;
}
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis =this.timeBetweenEvictionRunsMillis;
if(timeBetweenEvictionRunsMillis <=0L){
//默认60000ms,即1分钟
timeBetweenEvictionRunsMillis =60000L;
}
/*
1.如果连接空闲时间 < timeBetweenEvictionRunsMillis时间
2.连接空闲时间 > 0
不校验,也就是说我们如果通过开启testWhileIdle参数
校验连接有效性的话timeBetweenEvictionRunsMillis 时间⼀定不能超过8⼩时,
不然依然可能取到失效链接.
*/
if(idleMillis < timeBetweenEvictionRunsMillis && idleMillis >=0L){
break;
}
//执⾏校验 (即 : validationQuery中配置的'select 1 from dual'语句)
boolean validate =stConnectionInternal(poolableConnection.holder, );
if(validate){
break;
}
if(LOG.isDebugEnabled()){
LOG.debug("skip not validate connection.");
}
this.discardConnection(poolableConnection.holder);
}
}
veAbandoned){
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.setConnectedTimeNano();
this.activeConnectionLock.lock();
try{
this.activeConnections.put(poolableConnection, PRESENT);
this.activeConnections.put(poolableConnection, PRESENT);
}finally{
this.activeConnectionLock.unlock();
}
}
if(!this.defaultAutoCommit){
poolableConnection.setAutoCommit(false);
}
return poolableConnection;
}
第⼆种⽅式 : Destroy 定时任务检查需要被回收的连接
//init⽅法是线程池创建⽅法
public void init()throws SQLException {
...
//调⽤创建销毁线程⽅法
this.initedLatch.await();
...
}
/
/创建销毁线程
protected void createAndStartDestroyThread(){
this.destroyTask =new DruidDataSource.DestroyTask();
if(this.destroyScheduler != null){
long period =this.timeBetweenEvictionRunsMillis;
if(period <=0L){
period =1000L;
}
//启动销毁线程
this.destroySchedulerFuture =this.destroyScheduler.scheduleAtFixedRate(this.destroyTask, period, period, TimeUnit.MILLISECONDS);
untDown();
}else{
String threadName ="Druid-ConnectionPool-Destroy-"+ System.identityHashCode(this);
this.destroyConnectionThread =new DruidDataSource.DestroyConnectionThread(threadName);
this.destroyConnectionThread.start();
}
}
//销毁线程
public class DestroyTask implements Runnable {
public DestroyTask(){
}
public void run(){
/
/checkTime 为true
DruidDataSource.this.shrink(true, DruidDataSource.this.keepAlive);
if(DruidDataSource.this.isRemoveAbandoned()){
veAbandoned();
}
}
}
//具体⽅法
public void shrink(boolean checkTime,boolean keepAlive){
try{
this.lock.lockInterruptibly();
}catch(InterruptedException var49){
return;
}
boolean needFill =false;
int evictCount =0;
int evictCount =0;
int keepAliveCount =0;
int fatalErrorIncrement =this.fatalErrorCount -this.fatalErrorCountLastShrink;
this.fatalErrorCountLastShrink =this.fatalErrorCount;
int checkCount;
label956:{
try{
if(this.inited){
//可能被销毁数量 = 线程池当前线程数量 - 配置的最⼩空闲数
checkCount =this.poolingCount -this.minIdle;
long currentTimeMillis = System.currentTimeMillis();
int i;
for(i =0; i <this.poolingCount;++i){
DruidConnectionHolder connection =tions[i];
if((FatalError || fatalErrorIncrement >0)&&this.lastFatalErrorTimeMillis > tTimeMillis){
this.keepAliveConnections[keepAliveCount++]= connection;
}else if(checkTime){
long idleMillis;
if(this.phyTimeoutMillis >0L){
idleMillis = currentTimeMillis - tTimeMillis;
if(idleMillis >this.phyTimeoutMillis){
this.evictConnections[evictCount++]= connection;
continue;
}
}
//当前for循环处理的线程空闲时间 = 当前时间 - 连接最后活跃时间
idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;
if(idleMillis <this.minEvictableIdleTimeMillis && idleMillis <this.keepAliveBetweenTimeMillis){
break;
}
//连接空闲时间 >= 配置的最⼩空闲被回收时间 : minEvictableIdleTimeMillis
if(idleMillis >=this.minEvictableIdleTimeMillis){
/*
checkTime 为⽅法⼊参 = true,
i:当前for循环下标(连接取得时候是取得数组最⼤坐标,新创建的连接也是放在数组最⼤坐标上,所以0号坐标⼀定是最久未使⽤的那个)可能被销毁数量 = 线程池当前线程数量 - 配置的最⼩空闲数(checkCount = this.poolingCount - this.minIdle; )
重点 : 也就是minEvictableIdleTimeMillis配置只会回收超过minIdle的那部分空闲连接
*/
if(checkTime && i < checkCount){
this.evictConnections[evictCount++]= connection;
continue;
}
//连接空闲时间 > 配置的最⼤空闲时间maxEvictableIdleTimeMillis
//重点 : maxEvictableIdleTimeMillis参数会忽略配置的minIdle
if(idleMillis >this.maxEvictableIdleTimeMillis){
this.evictConnections[evictCount++]= connection;
continue;
}
}
if(keepAlive && idleMillis >=this.keepAliveBetweenTimeMillis){
this.keepAliveConnections[keepAliveCount++]= connection;
}
}else{
if(i >= checkCount){
break;
}
this.evictConnections[evictCount++]= connection;
}
}
i = evictCount + keepAliveCount;
if(i >0){//复制有效连接到连接池数组

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。