ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Druid源码解析(二):连接池初始化(init 方法)

2022-05-11 19:33:49  阅读:278  来源: 互联网

标签:初始化 dataSourceStat Druid init 源码 线程 new null


  在获取连接时,会执行初始化方法 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");
            }

 

四、加载 Filter 等初始化前期处理

  经过上面的校验后,开始做初始化的前期处理:使用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 并初始化 holder的数组

  生成 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流程总结

  init过程,对DruidDataSource进行了初始化操作,为了防止多线程并发场景下进行init操作,采用了Double Check的方式,配合ReentrentLock两次判断来实现。 详细流程如下图:

        

 

标签:初始化,dataSourceStat,Druid,init,源码,线程,new,null
来源: https://www.cnblogs.com/liconglong/p/16257629.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有