Druid源码分析(八)-- shrink
- Druid源码分析(八)-- shrink
- shrink
- 驱逐连接
- 保活连接
- 填充连接
- 总结
Druid源码分析(八)-- shrink
shrink
连接池在一些情况下会收缩
比如,在0.2.6之后,开始支持动态修改maxActive
当新设置的值小于原来的值,不会立刻关闭超出内容的部分,而是等到DestroyThread调度时做shrink才释放。
// DruidDataSource.java
public void shrink(boolean checkTime, boolean keepAlive) {
try {
// 如果连接被锁了,直接return
lock.lockInterruptibly();
} catch (InterruptedException e) {
return;
}
...
}
这里的lockInterruptibly 函数,前面几天的源码也有用到,今天查了下资料,发现是这样的:
lockInterruptibly 方法和 lock 方法类似,当有可用锁时会直接得到锁并立即返回,如果没有可用锁会一直等待直到获取锁,但和 lock 方法不同,lockInterruptibly 方法在等待获取时,如果遇到线程中断会放弃获取锁,并抛出异常。
获取锁之后,会循环连接池里的连接
// 收缩的连接数 = 连接池中存储的连接数-最小空闲连接数
final int checkCount = poolingCount - minIdle;
final long currentTimeMillis = System.currentTimeMillis();
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder connection = connections[i];
// 如果发生了致命异常,并且最后发生致命异常的时间大于连接创建的时间,就把连接加入保活连接池
if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis)) {
keepAliveConnections[keepAliveCount++] = connection;
continue;
}
// checkTime 默认是false
if (checkTime) {
...
} else {
// 把要收缩的连接加入驱逐连接池
if (i < checkCount) {
evictConnections[evictCount++] = connection;
} else {
break;
}
}
// 要移除的连接数=收缩的连接数+保活连接数
int removeCount = evictCount + keepAliveCount;
if (removeCount > 0) {
// 移除连接
System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
// 把连接池中移除的连接都置为null
Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
// 连接池中的连接数减掉移除的连接数
poolingCount -= removeCount;
}
keepAliveCheckCount += keepAliveCount;
// 如果连接池里的连接数 + activeCount < 最小空闲连接,则需要生成新连接,把needFill 置为true
if (keepAlive && poolingCount + activeCount < minIdle) {
needFill = true;
}
finally {
// 这里解锁函数开头获取的锁
lock.unlock();
}
驱逐连接
// 如果驱逐连接数大于0,就调用JdbcUtils 真正关闭连接
if (evictCount > 0) {
for (int i = 0; i < evictCount; ++i) {
DruidConnectionHolder item = evictConnections[i];
Connection connection = item.getConnection();
JdbcUtils.close(connection);
// 关闭连接数+1
destroyCountUpdater.incrementAndGet(this);
}
// 把驱逐连接池里的数据都置为null
Arrays.fill(evictConnections, null);
}
保活连接
// 如果保活连接数大于0
if (keepAliveCount > 0) {
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {
// 获取连接
DruidConnectionHolder holer = keepAliveConnections[i];
Connection connection = holer.getConnection();
holer.incrementKeepAliveCheckCount();
boolean validate = false;
try {
// 验证连接的有效性
this.validateConnection(connection);
validate = true;
}
...
boolean discard = !validate;
if (validate) {
holer.lastKeepTimeMillis = System.currentTimeMillis();
// 连接有效的话会把连接放在连接池最后
boolean putOk = put(holer, 0L, true);
// 如果连接没有成功加入连接池,就把丢弃标识置为true
if (!putOk) {
discard = true;
}
}
if (discard) {
try {
// 丢弃的连接会走回收逻辑
connection.close();
} catch (Exception e) {
// skip
}
lock.lock();
try {
// 丢弃连接数+1
discardCount++;
// 如果连接池里的连接数 + activeCount < 最小空闲连接,会生成新连接
if (activeCount + poolingCount <= minIdle) {
emptySignal();
}
} finally {
lock.unlock();
}
}
}
// 保活连接数+
this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
Arrays.fill(keepAliveConnections, null);
}
填充连接
// 如果填充标识为true
if (needFill) {
lock.lock();
try {
// 根据最小空闲连接 - (活跃连接+连接池里的连接+正在创建的连接)
int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
for (int i = 0; i < fillCount; ++i) {
// 创建连接
emptySignal();
}
} finally {
lock.unlock();
}
} else if (onFatalError || fatalErrorIncrement > 0) {
// 不需要填充但是有致命异常的话,也生成新连接
lock.lock();
try {
emptySignal();
} finally {
lock.unlock();
}
}
总结
这个shrink 函数先是用 lockInterruptibly 来获取锁,然后就是判断poolingCount、minIdle这些参数,和有没有致命异常等,去关闭连接、保活连接、创建连接。这个函数也分段用了很多次锁,再次强调了锁的粒度要尽可能小。