在获取连接时,会执行初始化方法 init() ,使用 DruidDataSource的入口。
为了保证不会重复初始化并且保证性能,使用了类似双重检测锁的方式来处理,第一次判断 inited 标识,如果已经初始化,则则直接返回,如果没有初始化,则使用ReentrantLock加锁处理,加锁成功,再次判断inited标识,inited使用volitle修饰,这里实际上就是使用了类似单例模式的双重检测锁,第一次判断保证性能,volitle保证可见性,加锁防止并发,第二次判断防止线程切换导致的多线程拿到锁。
public void init() throws SQLException { if (inited) { return; } // 获取驱动 // bug fixed for dead lock, for issue #2980 DruidDriver.getInstance(); // 加锁,防止重复初始化 final ReentrantLock lock = this.lock; try { lock.lockInterruptibly(); } catch (InterruptedException e) { throw new SQLException("interrupt", e); } boolean init = false; try { if (inited) { return; }
这一步没有太多可解析的内容,包括:生成当前线程的堆栈调用信息、生成数据源 ID、设置JDBC的相关参数、设置 jdbcUrl、初始化filter、设置DB类型(如mysql、Oracle等)、设置cacheServerConfiguration属性(是否缓存SHOW VARIABLESSHOW COLLATION命令的结果)
initStackTrace = Utils.toString(Thread.currentThread().getStackTrace()); this.id = DruidDriver.createDataSourceId(); if (this.id > 1) { long delta = (this.id - 1) * 100000; this.connectionIdSeedUpdater.addAndGet(this, delta); this.statementIdSeedUpdater.addAndGet(this, delta); this.resultSetIdSeedUpdater.addAndGet(this, delta); this.transactionIdSeedUpdater.addAndGet(this, delta); } if (this.jdbcUrl != null) { this.jdbcUrl = this.jdbcUrl.trim(); initFromWrapDriverUrl(); } for (Filter filter : filters) { filter.init(this); } if (this.dbTypeName == null || this.dbTypeName.length() == 0) { this.dbTypeName = JdbcUtils.getDbType(jdbcUrl, null); } DbType dbType = DbType.of(this.dbTypeName); if (dbType == DbType.mysql || dbType == DbType.mariadb || dbType == DbType.oceanbase || dbType == DbType.ads) { boolean cacheServerConfigurationSet = false; if (this.connectProperties.containsKey("cacheServerConfiguration")) { cacheServerConfigurationSet = true; } else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) { cacheServerConfigurationSet = true; } if (cacheServerConfigurationSet) { this.connectProperties.put("cacheServerConfiguration", "true"); } }
这一步主要是校验参数配置是否合理,例如最大活跃连接是否小于0、最大活跃连接是否小于最小连接等等。
if (maxActive <= 0) { throw new IllegalArgumentException("illegal maxActive " + maxActive); } if (maxActive < minIdle) { throw new IllegalArgumentException("illegal maxActive " + maxActive); } if (getInitialSize() > maxActive) { throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive); } if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) { throw new IllegalArgumentException("timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true"); } if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) { throw new SQLException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis"); } if (keepAlive && keepAliveBetweenTimeMillis <= timeBetweenEvictionRunsMillis) { throw new SQLException("keepAliveBetweenTimeMillis must be grater than timeBetweenEvictionRunsMillis"); }
经过上面的校验后,开始做初始化的前期处理:使用SPI机制加载 Filter 的实现类、处理驱动相关的配置、初始化校验、初始化异常存储、初始化 validConnectionChecker、检验连接查询。
// 初始化SPI initFromSPIServiceLoader(); // 处理驱动相关的配置 resolveDriver(); // 初始化校验 initCheck(); // 初始化异常存储 initExceptionSorter(); // 根据不同数据库初始化 validConnectionChecker initValidConnectionChecker(); // 检验连接查询 sql validationQueryCheck();
1、初始化SPI
SPI 机制是很多开源组件常用的扩展机制,例如在 JDK、dubbo、SpringBoot、Spring Cloud 中都有广泛的使用,在 Druid 中也使用了 SPI 机制加载,加载所有配置的 Filter。
private void initFromSPIServiceLoader() { if (loadSpifilterSkip) { return; } if (autoFilters == null) { List<Filter> filters = new ArrayList<Filter>(); ServiceLoader<Filter> autoFilterLoader = ServiceLoader.load(Filter.class); for (Filter filter : autoFilterLoader) { AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class); if (autoLoad != null && autoLoad.value()) { filters.add(filter); } } autoFilters = filters; } for (Filter filter : autoFilters) { if (LOG.isInfoEnabled()) { LOG.info("load filter from spi :" + filter.getClass().getName()); } addFilter(filter); } }
2、处理驱动相关的配置
这个没有什么特别的地方,只是根据不同的数据库驱动生成驱动的实例,对于 MockDriver、ClickHouse做了特殊处理。
protected void resolveDriver() throws SQLException { if (this.driver == null) { if (this.driverClass == null || this.driverClass.isEmpty()) { this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl); } if (MockDriver.class.getName().equals(driverClass)) { driver = MockDriver.instance; } else if ("com.alibaba.druid.support.clickhouse.BalancedClickhouseDriver".equals(driverClass)) { Properties info = new Properties(); info.put("user", username); info.put("password", password); info.putAll(connectProperties); driver = new BalancedClickhouseDriver(jdbcUrl, info); } else { if (jdbcUrl == null && (driverClass == null || driverClass.length() == 0)) { throw new SQLException("url not set"); } driver = JdbcUtils.createDriver(driverClassLoader, driverClass); } } else { if (this.driverClass == null) { this.driverClass = driver.getClass().getName(); } } }
3、初始化校验
这个就是对于数据库的各种校验,例如Oracle的版本和简单查询验证、db2的查询验证等
4、初始化异常存储
用于设置 exceptionSorter 属性,这是Druid连接池稳定性的保证,用于处理重大的不可恢复的异常,它是一个接口,不同的数据库有不同的实现类,在这里根据不同的驱动类型生成对应的 exceptionSorter。
5、初始化 validConnectionChecker
根据不同数据库初始化 validConnectionChecker,这个类很重要,用来检测连接池中连接的可用性,如果检测连接不可用,则close掉;可用的话就继续放回连接池中。
6、检验连接查询
检验连接查询 sql 是否正确执行。
生成 dataSourceStat:判断dataSourceStat是否采用了全局的dataSourceStat,如果使用了,则设置 dataSourceStat 的global属性,然后设置数据库类型,如果没有采用了全局的dataSourceStat,则新创建一个 dataSourceStat。
设置 dataSourceStat 的重置标志
创建 DruidConnectionHolder 数组,分别是所有连接的数组 connections、被驱逐连接数组 evictConnections、存活连接数组 keepAliveConnections,三个数组的长度都是最大连接数 maxActive。
// dataSourceStat是否采用了Global。对dataSourceStat进行set。 初始化holder的数组 if (isUseGlobalDataSourceStat()) { dataSourceStat = JdbcDataSourceStat.getGlobal(); if (dataSourceStat == null) { dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbTypeName); JdbcDataSourceStat.setGlobal(dataSourceStat); } if (dataSourceStat.getDbType() == null) { dataSourceStat.setDbType(this.dbTypeName); } } else { dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbTypeName, this.connectProperties); } dataSourceStat.setResetStatEnable(this.resetStatEnable); connections = new DruidConnectionHolder[maxActive]; evictConnections = new DruidConnectionHolder[maxActive]; keepAliveConnections = new DruidConnectionHolder[maxActive];
根据配置,进行异步初始化或同步初始化:
如果是异步初始化,调用 submitCreateTask() 进行处理。
如果非异步初始化,则创建物理连接。使用 poolingCount < initialSize 控制创建连接的数量,但是在默认的的初始化过程中,如果不通过其他配置参数指定,这个条件不会被触发,这可以看做是DruidDataSource的懒加载,只有真正需要Connection的时候,才会去创建物理的连接。
// 判断是否进行异步初始化 if (createScheduler != null && asyncInit) { // 如果异步初始化,调用通过submitCreateTask进行 for (int i = 0; i < initialSize; ++i) { submitCreateTask(true); } } else if (!asyncInit) { // 如果poolingCount < initialSize,则创建物理连接。但是在默认的的初始化过程中,如果不通过其他配置参数指定,这个条件不会被触发,这可以看做是DruidDataSource的懒加载,只有真正需要Connection的时候,才会去创建物理的连接。 // init connections while (poolingCount < initialSize) { try { PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection(); DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo); connections[poolingCount++] = holder; } catch (SQLException ex) { LOG.error("init datasource error, url: " + this.getUrl(), ex); if (initExceptionThrow) { connectError = ex; break; } else { Thread.sleep(3000); } } } if (poolingCount > 0) { poolingPeak = poolingCount; poolingPeakTime = System.currentTimeMillis(); } }
线程池初始化完成后,调用 createAndLogThread() 创建一个日志线程,但是这个线程的条件timeBetweenLogStatsMillis大于0,如果这个参数没有配置,日志线程不会创建。
调用 createAndStartCreatorThread() 方法创建一个创建连接的线程,并将创建的线程赋值给变量createConnectionThread
创建 DestroyTask对象,同时创建DestroyConnectionThread线程并start,将创建的线程赋值给变量 destroyConnectionThread。
如果keepAlive为true,还需调用submitCreateTask方法,将连接填充到minIdle,确保空闲的连接可用。
// 创建日志线程 但是这个线程的条件timeBetweenLogStatsMillis大于0,如果这个参数没有配置,日志线程不会创建。 createAndLogThread(); // 创建一个CreateConnectionThread对象,并启动。初始化变量createConnectionThread。 createAndStartCreatorThread(); // 创建 DestroyTask对象。同时创建DestroyConnectionThread线程,并start,初始化destroyConnectionThread。 createAndStartDestroyThread(); // 确保上述两个方法都执行完毕 initedLatch.await(); init = true; initedTime = new Date(); // 注册registerMbean registerMbean(); if (connectError != null && poolingCount == 0) { throw connectError; } // 如果keepAlive为true,还需调用submitCreateTask方法,将连接填充到minIdle。确保空闲的连接可用。 if (keepAlive) { // async fill to minIdle if (createScheduler != null) { for (int i = 0; i < minIdle; ++i) { submitCreateTask(true); } } else { this.emptySignal(); } }
所有的逻辑处理完成后,在 finally 中,会将初始化完成标志 inited 设置为 true,同时释放锁,最后判断 init和日志的INFO状态,打印一条init完成的日志。
finally { // 修改inited为true,并解锁。 inited = true; lock.unlock(); // 判断init和日志的INFO状态,打印一条init完成的日志。 if (init && LOG.isInfoEnabled()) { String msg = "{dataSource-" + this.getID(); if (this.name != null && !this.name.isEmpty()) { msg += ","; msg += this.name; } msg += "} inited"; LOG.info(msg); } }
init过程,对DruidDataSource进行了初始化操作,为了防止多线程并发场景下进行init操作,采用了Double Check的方式,配合ReentrentLock两次判断来实现。 详细流程如下图: