Druid连接池只存储在connections数组中,所以获取连贯的逻辑应该比HikariPool简略一些:间接从connectoins获取即可。
DruidDataSource.getConnection
间接上代码:
@Override
public DruidPooledConnection getConnection() throws SQLException {
return getConnection(maxWait);
}
调用了getConnection(maxWait),maxWait是参数设定的获取连贯的最长等待时间,超过该时长还没有获取到连贯的话,抛异样。
看getConnection(maxWait)代码:
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
init();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
}
先调用init,init办法会判断连接池是否曾经实现了初始化,如果没有实现初始化则首先进行初始化,初始化的代码咱们上一篇文章曾经剖析过了。
之后判断是否有filters,filters的内容咱们先放放,临时不论,间接看没有filters的状况下,调用getConnectionDirect办法。
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;
}
上来之后首先有限for循环,目标是从连接池获取到连贯之后,依据参数设定可能会做必要的查看,如果查看不通过(比方连贯不可用、连贯已敞开等等)的话循环从新获取。
而后调用getConnectionInternal获取连贯,getConnectionInternal办法应该是咱们明天文章的配角,咱们略微放一放,为了文章的可读性,先剖析完getConnectionDirect办法。
咱们假如通过调用getConnectionInternal办法获取到一个连贯(留神获取到的连贯对象是DruidPooledConnection,不是Connection对象,这个也不难想象,连接池获取到的连贯肯定是数据库物理连贯的代理对象(或者叫封装对象,封装了数据库物理连贯Connection对象的对象,这个原理咱们在剖析HikariPool的时候曾经说过了。这个DruidPooledConnection对象咱们也临时放一放,前面剖析)。
调用getConnectionInternal办法如果返回超时异样,判断:如果以后连接池没满,而且获取连贯超时重试次数小于参数notFullTimeoutRetryCount设定的次数的话,则continue,从新获取连贯。否则,抛出超时异样。
接下来:
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;
}
testOnBorrow参数的目标是:获取连贯后是否要做连贯可用性测试,如果设定为true的话,调用testConnectionInternal测试连贯的可用性,testConnectionInternal办法上一篇文章剖析连贯回收的时候、解决keepAlive的过程中就碰到过,就是执行配置好的sql语句测试连贯可用性,如果测试不通过的话则调用discardConnection敞开连贯,continue从新获取连贯。
否则,如果testOnBorrow参数没有关上的话,查看以后连贯如果曾经敞开,则调用discardConnection敞开连贯(没太明确连贯既然曾经是敞开状态,为啥还须要调用?),continue从新获取连贯。
不倡议关上testOnBorrow参数,因为连接池都会有连贯回收机制,比方上一篇文章讲过的Druid的DestroyConnectionThread & DestroyTask,回收参数配置失常的话,回收机制根本能够确保连贯的可用性。关上testOnBorrow参数会导致每次获取连贯之后都测试连贯的可用性,重大影响零碎性能。
接下来:
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;
}
}
}
}
这段代码的逻辑是:参数testWhileIdle设置为true的话,查看以后链接的闲暇时长如果大于timeBetweenEvictionRunsMillis(默认60秒)的话,则调用testConnectionInternal测试连贯可用性,连贯不可用则敞开连贯,continue从新获取连贯。
而后:
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano();
poolableConnection.traceEnable = true;
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT);
} finally {
activeConnectionLock.unlock();
}
}
呈现了一个removeAbandoned参数,这个参数的意思是移除被遗弃的连贯对象,如果关上的话就把以后连贯放到activeConnections中,篇幅无限,这部分内容就不开展了,前面咱们会专门写一篇文章介绍removeAbandoned参数。
剩下的一小部分代码,很简略,依据参数设置连贯的autoCommit,之后返回连贯poolableConnection。
if (!this.defaultAutoCommit) {
poolableConnection.setAutoCommit(false);
}
return poolableConnection;
}
}
getConnectionDirect办法源码剖析实现了,上面咱们要看一下getConnectionInternal办法,这是真正从连接池中获取连贯的办法。
getConnectionInternal
间接看代码:
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
if (closed) {
connectErrorCountUpdater.incrementAndGet(this);
throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
}
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
if (disableException != null) {
throw disableException;
}
throw new DataSourceDisableException();
}
final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
final int maxWaitThreadCount = this.maxWaitThreadCount;
DruidConnectionHolder holder;
查看连接池状态如果曾经disable或cloesed的话,抛异样。
接下来:
for (boolean createDirect = false;;) {
if (createDirect) {
createStartNanosUpdater.set(this, System.nanoTime());
if (creatingCountUpdater.compareAndSet(this, 0, 1)) {
PhysicalConnectionInfo pyConnInfo = DruidDataSource.this.createPhysicalConnection();
holder = new DruidConnectionHolder(this, pyConnInfo);
holder.lastActiveTimeMillis = System.currentTimeMillis();
creatingCountUpdater.decrementAndGet(this);
directCreateCountUpdater.incrementAndGet(this);
if (LOG.isDebugEnabled()) {
LOG.debug("conn-direct_create ");
}
boolean discard = false;
lock.lock();
try {
if (activeCount < maxActive) {
activeCount++;
holder.active = true;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
break;
} else {
discard = true;
}
} finally {
lock.unlock();
}
if (discard) {
JdbcUtils.close(pyConnInfo.getPhysicalConnection());
}
}
}
初始化createDirect变量为false之后启动有限循环,意思是一直循环直到获取到连贯、或超时等其余异常情况产生。
紧接着的这段代码是createDirect=true的状况下执行的,createDirect是在上面循环体中查看如果:createScheduler不为空、连接池空、流动连接数小于设定的最大流动连接数maxActive、并且createScheduler的队列中排队期待创立连贯的线程大于0的状况下,设置createDirect为true的,以上这些条件如果成立的话,大概率表明createScheduler中的创立线程出问题了、所以createScheduler大概率指望不上了,所以要间接创立连贯了。
间接创立的代码也很容易了解,调用createPhysicalConnection创立物理连贯,创立DruidConnectionHolder封装该物理连贯,创立之后获取锁资源,查看activeCount < maxActive则表明创立连贯胜利、完结for循环,否则,activeCount >= maxActive则阐明违反了准则(间接创立连贯的过程中createScheduler可能复活了、又创立进去连贯放入连接池中了),所以,敞开锁资源之后,将刚创立进去的连贯敞开。
而后:
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}
try {
if (maxWaitThreadCount > 0
&& notEmptyWaitThreadCount >= maxWaitThreadCount) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
if (onFatalError
&& onFatalErrorMaxActive > 0
&& activeCount >= onFatalErrorMaxActive) {
connectErrorCountUpdater.incrementAndGet(this);
StringBuilder errorMsg = new StringBuilder();
errorMsg.append("onFatalError, activeCount ")
.append(activeCount)
.append(", onFatalErrorMaxActive ")
.append(onFatalErrorMaxActive);
if (lastFatalErrorTimeMillis > 0) {
errorMsg.append(", time '")
.append(StringUtils.formatDateTime19(
lastFatalErrorTimeMillis, TimeZone.getDefault()))
.append("'");
}
if (lastFatalErrorSql != null) {
errorMsg.append(", sql \n")
.append(lastFatalErrorSql);
}
throw new SQLException(
errorMsg.toString(), lastFatalError);
}
connectCount++;
if (createScheduler != null
&& poolingCount == 0
&& activeCount < maxActive
&& creatingCountUpdater.get(this) == 0
&& createScheduler instanceof ScheduledThreadPoolExecutor) {
ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
if (executor.getQueue().size() > 0) {
createDirect = true;
continue;
}
}
获取锁资源,查看期待获取连贯的线程数如果大于参数设置的最大期待线程数,抛异样。
查看并解决异样。
累加connectCount。
之后是下面提到过的对createDirect的解决。
接下来到了最为要害的局部,个别状况下createDirect为false,不会间接创立连贯,逻辑会走到上面这部分代码中,从连接池中获取连贯:
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
if (holder != null) {
if (holder.discard) {
continue;
}
activeCount++;
holder.active = true;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
}
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException(e.getMessage(), e);
} catch (SQLException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw e;
} finally {
lock.unlock();
}
如果参数设置了maxWait,则调用pollLast限时获取,否则调用takeLast获取连贯,这两个办法稍后剖析。
之后查看获取到的连贯曾经被discard的话,continue从新获取连贯。
开释锁资源。
从连接池中获取到了连贯,完结for循环。
如果takeLast或poolLast返回的DruidConnectionHolder为null的话(调用poolLast超时),处理错误信息,抛GetConnectionTimeoutException超时异样(这部分代码没有贴出,省略了……感兴趣的童鞋本人关上源码看一下)。
否则,用DruidConnectionHolder封装创立DruidPooledConnection后返回。
takeLast & pollLast(nanos)
这两个办法的逻辑其实差不多,次要区别一个是限时,一个不限时,两个办法都是在锁状态下执行。
具体调用哪一个办法取决于参数maxWait,默认值为-1,默认状况下会调用takeLast,获取连贯的时候不限时。
倡议设置maxWait,否则在非凡状况下如果创立连贯失败、会导致应用层线程挂起,获取不到任何返回的状况呈现。如果设置了maxWait,getConnection办法会调用pollLast(nanos),获取不到连贯后,应用层会失去连贯超时的反馈。
先看takeLast办法:
takeLast() throws InterruptedException, SQLException {
try {
while (poolingCount == 0) {
emptySignal(); // send signal to CreateThread create connection
if (failFast && isFailContinuous()) {
throw new DataSourceNotAvailableException(createError);
}
notEmptyWaitThreadCount++;
if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
}
try {
notEmpty.await(); // signal by recycle or creator
} finally {
notEmptyWaitThreadCount--;
}
notEmptyWaitCount++;
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
if (disableException != null) {
throw disableException;
}
throw new DataSourceDisableException();
}
}
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
notEmptySignalCount++;
throw ie;
}
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
return last;
}
如果连接池为空(poolingCount == 0)的话,有限循环。
调用emptySignal(),告诉创立连接线程,有人在期待获取连贯,抓紧时间创立连贯。
而后调用notEmpty.await(),期待创立连接线程在实现创立、或者有连贯偿还到连接池中后唤醒告诉。
如果产生异样,调用一下notEmpty.signal()告诉其余获取连贯的线程,没准本人没能获取胜利、其余线程能获取胜利。
上面的代码,线程池肯定不空了。
线程池的线程数量减1(decrementPoolingCount),而后获取connections的最初一个元素返回。
pollLast办法的代码逻辑和takeLast的相似,只不过线程池空的话,以后线程会限时挂起期待,超时依然不能获取到连贯的话,间接返回null。
Druid连接池获取连贯代码剖析结束!
小结
Druid连接池的连贯获取过程的源码剖析结束,前面还有连贯偿还过程,下一篇文章持续剖析。
Thanks a lot!
上一篇 连接池 Druid (二) – 连贯回收 DestroyConnectionThread & DestroyTask
发表回复