Druid 连接池部分源码解读
从 Druid SpringBoot 启动说起
被注入的 DataSource 是什么
首先我们来看一下正常java web代码中 javax.sql.DataSource
是一个怎么样的实体,有以下一段代码
@RestController
@RequestMapping("/druidTest")
public class DruidConnection {
@Autowired
private DataSource dataSource;
@Autowired
private UserMapper userMapper;
@GetMapping("/connection")
public void dataSourceTest()throws Exception{
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("select * from user");
ResultSet resultSet = statement.executeQuery();
List<User> all = userMapper.findALL();
}
}
可以看出 dataSource 是一个 DruidDataSourceWrapper
的对象
以下是 DruidDataSourceWrapper
的代码
@ConfigurationProperties("spring.datasource.druid")
class DruidDataSourceWrapper extends DruidDataSource implements InitializingBean {
@Autowired
private DataSourceProperties basicProperties;
@Override
public void afterPropertiesSet() throws Exception {
//if not found prefix 'spring.datasource.druid' jdbc properties ,'spring.datasource' prefix jdbc properties will be used.
if (super.getUsername() == null) {
super.setUsername(basicProperties.determineUsername());
}
if (super.getPassword() == null) {
super.setPassword(basicProperties.determinePassword());
}
if (super.getUrl() == null) {
super.setUrl(basicProperties.determineUrl());
}
if(super.getDriverClassName() == null){
super.setDriverClassName(basicProperties.getDriverClassName());
}
}
...
}
然后这个类在 DruidDataSourceAutoConfigure
当中被使用到了,这个SpringBoot Starter 的代码配置还是相对比较简单的
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
DruidStatViewServletConfiguration.class,
DruidWebStatFilterConfiguration.class,
DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
这里有个地方可以研究一下 @Bean(initMethod = "init")
我们来看一下 Spring 是怎么描述的
org.springframework.context.annotation.Bean
/**
* The optional name of a method to call on the bean instance during initialization.
* Not commonly used, given that the method may be called programmatically directly
* within the body of a Bean-annotated method.
* <p>The default value is {@code ""}, indicating no init method to be called.
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
String initMethod() default "";
我认为大致意思就是在实例化对象之后会去执行的代码,但是不太清楚与afterPropertiesSet()
这个方法谁更快的被执行,这个我还是需要对Spring Bean 的生命周期有更加深刻的理解认知。应该也是Spring Bean 生命周期中间的某个阶段会被执行到的初始化方法。
Debug 亲测 afterPropertiesSet()
在 @Bean(initMethod = "init")
之前执行
根据debug测试情况为 依次执行 构造函数 -> 属性注入 -> afterPropertiesSet()
-> @Bean(initMethod = "init")
根据 Debug 测试的结果 找到了一点 Spring Bean 初始化的蛛丝马迹 在 AbstractAutowireCapableBeanFactory
类中有一个 invokeInitMethods
的方法
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((InitializingBean) bean).afterPropertiesSet();
return null;
}, getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
在代码中找到了SpringBoot 2.0初始化顺序的有力证据,先进行判断是否是一个InitializingBean
的对象,如果是就开始执行 afterPropertiesSet()
的方法,之后在进行判断 RootBeanDefinition
中initMethodName
是存在,如果存在再执行 init方法
为了了解spring bean 的生命周期,根据deug在AbstractAutowireCapableBeanFactory
中找到了一些线索,来佐证。以下是该类的initializeBean
方法
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
//应用容器初始化前置方法执行
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//执行装配方法 包括 1. afterPropertiesSet 2. initMethodName 等
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
//应用容器初始化后置方法执行
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
先执行applyBeanPostProcessorsBeforeInitialization
方法就是执行BeanPostProcessor
接口的postProcessBeforeInitialization
方法 同理另外一个applyBeanPostProcessorsAfterInitialization
是执行BeanPostProcessor
的postProcessAfterInitialization
Druid 获取连接
DruidDataSource
getConnection
是如何获取到jdbc 的连接
@Override
public DruidPooledConnection getConnection() throws SQLException {
return 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()
方法中的代码太长了,就不在这里贴出来了
其中大致过程如下
- 首先进行判断是否已经完成了初始化 若已经初始化完成直接返回
- 使用锁进行同步操作的初始化
- 其中使用了锁双重判断机制(单例模式中经常会提到)
- 根据jdbc的url地址判断类型
- 根据数据库驱动名称获取到数据库的类型
- 如果是Mysql,MariaDB,阿里云RDS 有进行
cacheServerConfiguration
的操作
- 如果是Mysql,MariaDB,阿里云RDS 有进行
- 进行一系列的参数数组校验(连接数量,时间等)
- 反射获取数据库
driver
的Class对象 - 验证驱动
initCheck()
- 初始化一个异常的排序器
initExceptionSorter()
- 初始化连接验证器
initValidConnectionChecker()
- 验证查询结果
validationQueryCheck()
- 不太确定的代码 -> 全局数据源
- 也是不太确定的代码 -> 多线程方式初始化启动 还使用了
CountDownLatch
- 完成初始化过程,释放锁 方法结束
其实其中还是有不少问题等着我去深一步的了解
其中
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
这一段根据代码阅读发现使用了回调,在filterChain
中的某些filter
会继续回调到DataSource
的这个方法,导致最后会使用return getConnectionDirect(maxWaitMillis);
所以getConnectionDirect
这个方法还是需要看一下的
DruidDataSource
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
//使用死循环的方式不断去获取
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
//尝试获取到池化的jdbc连接
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) { //捕获获取连接超时的异常
//判断现在当前连接池是不是小于最大连接数量,比最大连接数小继续获取,值+1,否则就抛异常
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
//日志输出
if (LOG.isWarnEnabled()) {
LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}
//如果申请连接进行测试 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
if (testOnBorrow) {
//具体的验证
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
//判断失败
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
Connection realConnection = poolableConnection.conn;
//抛弃连接,不进行回收,而是抛弃 (源代码就这么写的)
discardConnection(realConnection);
//继续申请连接操作
continue;
}
} else {
Connection realConnection = poolableConnection.conn;
if (poolableConnection.conn.isClosed()) {
discardConnection(null); // 传入null,避免重复关闭
continue;
}
//大概是判断两次申请的时间间隔 空闲时间
if (testWhileIdle) {
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = poolableConnection.holder.lastActiveTimeMillis;
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(realConnection);
continue;
}
}
}
}
//连接泄漏监测
//https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B
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();
}
}
if (!this.defaultAutoCommit) {
poolableConnection.setAutoCommit(false);
}
return poolableConnection;
}
}
可以看出其实就是根据poolableConnection = getConnectionInternal(maxWaitMillis);
这句核心的代码获取到一个池化的jdbc连接,然后进行多次的判断,如申请连接时的校验,检查空闲时间官方GitHub的参数解释可以参考一下,然后就是连接泄漏的监视,这里也有官方GitHub的介绍
所以我们可以先看看poolableConnection = getConnectionInternal(maxWaitMillis);
这句代码在干什么
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
...
DruidConnectionHolder holder;
//不断的尝试去获取连接
for (boolean createDirect = false;;) {
//一些连接获取时的检查
...
//使用锁进行同步
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}
try {
//等待超时相关的操作 ... (源码略)
//发生致命的错误... (源码略)
//检查... (源码略)
//根据等待时间选择不同的方式获取连接 在Druid世界中就是 DruidConnectionHolder 对象
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
...
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException(e.getMessage(), e);
} catch (SQLException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw e;
} finally {
lock.unlock();
}
break;
}
//如果获取到的 DruidConnectionHolder holder 为null 必然会抛异常了
if (holder == null) {
long waitNanos = waitNanosLocal.get();
...
if (this.createError != null) {
throw new GetConnectionTimeoutException(errorMessage, createError);
} else {
throw new GetConnectionTimeoutException(errorMessage);
}
}
holder.incrementUseCount();
//使用一个 DruidPooledConnection 类对象对 holder 进行封装返回
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;
}
从上面的代码可以看出来在druid获取到连接之前会进行锁同步操作,会进行一系列的判断,当然归根结底还是靠着 holder = pollLast(nanos);
和 holder = takeLast();
来获取 DruidConnectionHolder
对象进行包装返回的。
下面以holder = takeLast();
为例简单一点的来介绍Druid
如何从自己池中获取连接返回的
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
try {
while (poolingCount == 0) {
emptySignal(); // send signal to CreateThread create connection
...
try {
//在启动过程中就是在等待池中连接非空的信号
notEmpty.await(); // signal by recycle or creator
//notEmpty 是一个成员变量 Condition 对象
} finally {
notEmptyWaitThreadCount--;
}
notEmptyWaitCount++;
...
}
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
notEmptySignalCount++;
throw ie;
}
//从当前对象的一个属性(一个数组去获取对象)获取到之后将数组的对应位置为null
decrementPoolingCount();
//每次都是从最后一个开始去获取的
DruidConnectionHolder last = connections[poolingCount];//poolingCount 成员变量
connections[poolingCount] = null;
return last;
}
从上面的代码可以看出还是比较直观看到是Druid
内部是使用数组进行池化存储的,唯一一个需要注意的是用到了Condition
释放信号去获取连接以及等待,主要是在poolingCount
==0的时候会出现。个人感觉类似于生产者消费者模型
根据debug在spring boot启动时第一次获取连接处打的断点可以看到有以下几个相关的线程
- “main”@1 in group “main”: RUNNING 主线程
- “Druid-ConnectionPool-Create-75480150”@5,100 in group “main”: WAIT 创建连接的线程
- “Druid-ConnectionPool-Destroy-75480150”@5,103 in group “main”: WAIT 销毁连接的线程
这两个创建/销毁连接的线程分别在init()
中被创建
init()
方法
createAndStartCreatorThread();
createAndStartDestroyThread();
根据debug 情况启动spring boot 项目时当代码执行到 emptySignal();
时会唤醒另外一个线程即Druid-ConnectionPool-Create
我们看看这个线程的代码是在干什么的
//这个类是 DruidDataSource 的内部类
public class CreateConnectionThread extends Thread {
public CreateConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
long lastDiscardCount = 0;
int errorCount = 0;
//还是
for (;;) {
// addLast 同步处理
try {
lock.lockInterruptibly();
} catch (InterruptedException e2) {
break;
}
...
try {
boolean emptyWait = true;
...
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& !(keepAlive && activeCount + poolingCount < minIdle)) {
empty.await();
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
} catch (InterruptedException e) {
... //日志打印相关
break;
} finally {
lock.unlock();
}
PhysicalConnectionInfo connection = null;
try {
//获取物理连接(本质上还是jdbc driver来创建获取的)
connection = createPhysicalConnection();
setFailContinuous(false);
} catch (SQLException e) {
...
} catch (RuntimeException e) {
...
} catch (Error e) {
...
}
if (connection == null) {
continue;
}
//相对关键的代码将获取的到物理连接(原生jdbc连接的包装类)放到数组中池化
boolean result = put(connection);
//放置失败就关闭连接
if (!result) {
JdbcUtils.close(connection.getPhysicalConnection());
LOG.info("put physical connection to pool failed.");
}
errorCount = 0; // reset errorCount
}
}
}
从上面代码看出基本上逻辑还是相对清晰的,每次需要获取新的连接(底层jdbc连接不够用了需要新的连接)就利用这个单独的线程去创建jdbc连接然后包装成物理连接进而给put(connection);
放置到数组中进行池化,所以接下来就看看put
做了什么动作
protected boolean put(PhysicalConnectionInfo physicalConnectionInfo) {
DruidConnectionHolder holder = null;
try {
holder = new DruidConnectionHolder(DruidDataSource.this, physicalConnectionInfo);
} catch (SQLException ex) {
lock.lock();
try {
if (createScheduler != null) {
createTaskCount--;
}
} finally {
lock.unlock();
}
LOG.error("create connection holder error", ex);
return false;
}
//本周上还是用到了下面的put方法
return put(holder);
}
private boolean put(DruidConnectionHolder holder) {
lock.lock();
try {
//当钱池中的连接数量 >= 最大连接数量 放置失败
if (poolingCount >= maxActive) {
return false;
}
//把连接池的第poolingCount位设置为目标DruidConnectionHolder
//可以联系上面takeLast()方法代码,每次都是从数组最后开始获取连接返回的
connections[poolingCount] = holder;
//增加当前池中的连接数量
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
//释放一个非空的信号,生产者,消费者模型 目测应该是通知使用的线程池中连接非空才可以获取
notEmpty.signal();
//非空信号释放次数++
notEmptySignalCount++;
if (createScheduler != null) {
createTaskCount--;
if (poolingCount + createTaskCount < notEmptyWaitThreadCount //
&& activeCount + poolingCount + createTaskCount < maxActive) {
emptySignal();
}
}
} finally {
lock.unlock();
}
return true;
}
至此初始化过程中Druid是如何创建一个连接并且放置到池中进行池化的过程就就结束了。
初始化过程就看看上面对init()方法的介绍,下面简单说一下Durid
获取一个连接的动作做了什么东西
获取一个连接的过程大致如下
1. DruidDataSource#getConnection()
最初暴露给外部使用的方法,返回值是一个DruidPooledConnection
对象同时实现了多个接口,包括javax.sql.PooledConnection
和java.sql.Connection
其中还有一个重载的getConnection
方法,需要传入一个超时时间,在该方法中用到了递归与回调,经过多个Filter
默认的主要是统计相关的StatFilter
这个类,最后本质上是执行getConnectionDirect
方法获取连接
2. DruidDataSource#getConnectionDirect(long)
这个方法其实也是只是一个包装的过度方法,主要还是执行了getConnectionInternal
这个方法,但是在执行完成getConnectionInternal
方法后根据配置在获取到连接之后执行了一些判断校验,Durid GitHub有关于这些方面的介绍
3. DruidDataSource#getConnectionInternal(long)
在这个方法里主要还是对 pollLast(nanos)
和 takeLast()
作了一层包装,再加上了一些锁同步机制的判断,和异常的包装处理
4. DruidDataSource#takeLast()
最核心直接的方法,利用了Connection
的机制,实现了生成者,消费者模型的实现。在Druid
内部还是利用了数组进行池化的,每次均从数组的最后进行获取
有关于生产者,消费者模型的介绍,大家可以看看 Druid连接池原理学习这篇文章,其中第五部分对连接池的同步策略进行了介绍
Durid 如何 clsoe 归还一个连接
因为使用的Druid
之后所以的Connection
均使用了DruidPooledConnection
这个实现类,所以我们来看看DruidPooledConnection
是怎么clsoe的
@Override
public void close() throws SQLException {
//如果当前连接已经关闭则直接返回不作处理
if (this.disable) {
return;
}
//如果DruidConnectionHolder 为空打个日志就结束了
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
//获取线程对象进行判断是否为同一个线程进行归还连接操作
DruidAbstractDataSource dataSource = holder.getDataSource();
boolean isSameThread = this.getOwnerThread() == Thread.currentThread();
//设置连接为异步关闭操作
if (!isSameThread) {
dataSource.setAsyncCloseConnectionEnable(true);
}
//如果是异步关闭连接,则进行同步连接关闭的操作
if (dataSource.isAsyncCloseConnectionEnable()) {
syncClose();
return;
}
//执行ConnectionEventListener在关闭连接时执行
for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
listener.connectionClosed(new ConnectionEvent(this));
}
//执行过滤器链的归还操作/单纯归还操作
List<Filter> filters = dataSource.getProxyFilters();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(dataSource);
filterChain.dataSource_recycle(this);
} else {
recycle();
}
this.disable = true;
}
上面代码中syncClose();
同步关闭的代码逻辑与后续操作其实是类似的
public void syncClose() throws SQLException {
lock.lock();
try {
if (this.disable) {
return;
}
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
listener.connectionClosed(new ConnectionEvent(this));
}
DruidAbstractDataSource dataSource = holder.getDataSource();
List<Filter> filters = dataSource.getProxyFilters();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(dataSource);
filterChain.dataSource_recycle(this);
} else {
recycle();
}
this.disable = true;
} finally {
lock.unlock();
}
}
可以看出其实执行的逻辑与上一段close
方法的代码逻辑是一样的,其中多了一些同步的操作,和重复了一些关闭之前需要执行的判断。
所以我们可以看看filterChain.dataSource_recycle(this);
这句代码是作了什么动作,根据上文中的介绍,Druid
的套路应该也是回调+递归然后执行一个关键操作,因为getConnection
也是这么搞的com.alibaba.druid.filter.FilterChainImpl
@Override
public void dataSource_recycle(DruidPooledConnection connection) throws SQLException {
if (this.pos < filterSize) {
nextFilter().dataSource_releaseConnection(this, connection);
return;
}
//所以归根到底还是执行了DruidPooledConnection.recycle()方法
connection.recycle();
}
所以现在我们回到DruidPooledConnection
中看看核心的recycle
在做什么
public void recycle() throws SQLException {
if (this.disable) {
return;
}
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
if (!this.abandoned) {
//关键步骤,还是得看DruidAbstractDataSource 是怎么回收连接的
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.recycle(this);
}
this.holder = null;
conn = null;
transactionInfo = null;
closed = true;
}
DruidDataSource
是DruidAbstractDataSource
的实现类对recycle
进行了实现代码如下
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.holder;
//一下代码判空和逻辑校验...
final Connection physicalConnection = holder.conn;
...
final boolean isAutoCommit = holder.underlyingAutoCommit;
final boolean isReadOnly = holder.underlyingReadOnly;
final boolean testOnReturn = this.testOnReturn;
try {
// check need to rollback?
if ((!isAutoCommit) && (!isReadOnly)) {
pooledConnection.rollback();
}
// reset holder, restore default settings, clear warnings
boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
//多线程情况,估计是异步的问题
if (!isSameThread) {
final ReentrantLock lock = pooledConnection.lock;
lock.lock();
try {
holder.reset();
} finally {
lock.unlock();
}
} else {
holder.reset(); // reset default settings
}
if (holder.discard) {
return;
}
if (physicalConnection.isClosed()) {
lock.lock();
try {
activeCount--;
closeCount++;
} finally {
lock.unlock();
}
return;
}
if (testOnReturn) {
//归还测试...
}
if (!enable) {
discardConnection(holder.conn);
return;
}
boolean result;
final long lastActiveTimeMillis = System.currentTimeMillis();
lock.lock();
try {
activeCount--;
closeCount++;
//关键归还操作
result = putLast(holder, lastActiveTimeMillis);
recycleCount++;
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(holder.conn);
LOG.info("connection recyle failed.");
}
} catch (Throwable e) {
...
}
}
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis;
//当前DruidConnectionHolder对象放置到数组的第一个空的位置,取/还 连接均从后往前
connections[poolingCount] = e;
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = lastActiveTimeMillis;
}
//释放池中连接非空的信号
notEmpty.signal();
notEmptySignalCount++;
return true;
}
主要是就是两个的步骤,一个是把DruidConnectionHolder
放到最后,另外一个是释放非空的信号。至此在使用Druid
连接池关闭归还连接的过程也就结束了。
归还连接步骤
1. DruidPooledConnection#close()
对外暴露使用的close
方法,也是最为常用的关闭连接归还连接的方法。里面根据申请连接的线程与当前线程进行匹配发现是否为相同线程选择是否执行syncClose();
同步回收,还是当前线程直接回收,执行recycle()方法,然后执行Druid
拦截器链作后续处理,本质也还是recycle()
这个方法
2. DruidPooledConnection#recycle()/syncClose()
很简单,就是调用了DruidDataSource
的recycle
方法,和一些判断校验,日志输出等。
3. DruidDataSource#recycle(DruidPooledConnection pooledConnection)
回收连接的方法,通过判断是否为不同线程来决定是否使用同步的方法执行 holder.reset();
将连接池相关配置修改为默认配置,最后执行putLast
方法将连接归还到池中
4. DruidDataSource#putLast(DruidConnectionHolder,long)
将连接放到数组的最后位置上,把当前池中的连接数量增加,释放一个当前池中数据非空的信号,对应的统计记录++,至此Druid
归还一个连接就到此全部结束了
结尾
文章到此就结束了,在这里我想感谢以下几位博文的作者,是他们先在网络上撰写了一些关于Druid
连接池相关的文章,为除了我之外更多的人提供相应的帮助,直接促使我有一定的能力和素材编写这篇文章
-
bbsmax Druid源码阅读之连接池 主要介绍了连接获取和归还的大致脉络,深入浅出的介绍了连接池的一个池化的大体思路方向
-
大诚挚 Druid数据库连接池源码分析 在
DruidConnectionHolder
类的介绍给了我很多的启发 -
乌啼 druid 连接池源码分析 对
connection
的层次和责任链作了比较好的介绍,还有不少的图片可以让读者很直观的看到作者想要表达的思路 -
阿里加多 Druid连接池原理学习 对整个
Druid
的执行流程作了总体性的介绍,执行流程部分有图可以直观的看出来,而且在同步策略的介绍给为阅读源码提供了一定的帮助
我的这篇文章很多内容均是参考上面各位大佬的文章,希望看到这里的读者也可以去阅读一下他们的文章,应该会有更多不一样的收货,谢谢。