Druid 连接池部分源码解读

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()的方法,之后在进行判断 RootBeanDefinitioninitMethodName是存在,如果存在再执行 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是执行BeanPostProcessorpostProcessAfterInitialization

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 的操作
  • 进行一系列的参数数组校验(连接数量,时间等)
  • 反射获取数据库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.PooledConnectionjava.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;
}

DruidDataSourceDruidAbstractDataSource的实现类对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()

很简单,就是调用了DruidDataSourcerecycle方法,和一些判断校验,日志输出等。

3. DruidDataSource#recycle(DruidPooledConnection pooledConnection)

回收连接的方法,通过判断是否为不同线程来决定是否使用同步的方法执行 holder.reset();将连接池相关配置修改为默认配置,最后执行putLast方法将连接归还到池中

4. DruidDataSource#putLast(DruidConnectionHolder,long)

将连接放到数组的最后位置上,把当前池中的连接数量增加,释放一个当前池中数据非空的信号,对应的统计记录++,至此Druid归还一个连接就到此全部结束了

结尾

文章到此就结束了,在这里我想感谢以下几位博文的作者,是他们先在网络上撰写了一些关于Druid连接池相关的文章,为除了我之外更多的人提供相应的帮助,直接促使我有一定的能力和素材编写这篇文章

  • bbsmax Druid源码阅读之连接池 主要介绍了连接获取和归还的大致脉络,深入浅出的介绍了连接池的一个池化的大体思路方向

  • 大诚挚 Druid数据库连接池源码分析DruidConnectionHolder类的介绍给了我很多的启发

  • 乌啼 druid 连接池源码分析connection的层次和责任链作了比较好的介绍,还有不少的图片可以让读者很直观的看到作者想要表达的思路

  • 阿里加多 Druid连接池原理学习 对整个Druid的执行流程作了总体性的介绍,执行流程部分有图可以直观的看出来,而且在同步策略的介绍给为阅读源码提供了一定的帮助

我的这篇文章很多内容均是参考上面各位大佬的文章,希望看到这里的读者也可以去阅读一下他们的文章,应该会有更多不一样的收货,谢谢。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值