Druid source code analysis: connection pool initialization (init method)

When obtaining the connection, the initialization method init() will be executed, using the entry of DruidDataSource.

1, Dual detection ensures concurrency security and performance

In order to ensure that the initialization will not be repeated and ensure the performance, a method similar to double detection lock is used to process. The initial identification is judged for the first time. If it has been initialized, it will be returned directly. If it has not been initialized, re entrantlock is used to lock it. The locking is successful. The initial identification is judged again. The initial is modified with volitle. In fact, a double detection lock similar to the singleton mode is used here, The first judgment ensures performance, the volatile ensures visibility, locks to prevent concurrency, and the second judgment prevents multiple threads from getting locks caused by thread switching.

    public void init() throws SQLException {
        if (inited) {
            return;
        }

        // Get driver
        // bug fixed for dead lock, for issue #2980
        DruidDriver.getInstance();

        // Lock to prevent repeated initialization
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }

        boolean init = false;
        try {
            if (inited) {
                return;
            }

 

2, Attribute generation and setting

There is not much to resolve in this step, including: generating the stack call information of the current thread, generating the data source ID, setting the relevant parameters of JDBC, setting the JDBC URL, initializing the filter, setting the DB type (such as mysql, Oracle, etc.), setting the cacheServerConfiguration property (whether to cache the result of the show variablesshow collection command)

            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");
                }
            }

 

3, Parameter configuration verification

This step is mainly to verify whether the parameter configuration is reasonable, such as whether the maximum active connection is less than 0, whether the maximum active connection is less than the minimum connection, and so on.

            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");
            }

 

4, Pre initialization processing such as loading Filter

After the above verification, start the pre-processing of initialization: use the SPI mechanism to load the implementation class of # Filter, process the configuration related to the driver, initialize the verification, initialize the exception storage, initialize # validConnectionChecker, and verify the connection query.

            // initialization SPI
            initFromSPIServiceLoader();

            // Handling driver related configurations
            resolveDriver();

            // Initialization check
            initCheck();

            // Initialize exception store
            initExceptionSorter();

            // Initialize according to different databases validConnectionChecker
            initValidConnectionChecker();

            // Check connection query sql
            validationQueryCheck();

1. Initialize SPI

SPI mechanism is a common extension mechanism for many open source components. For example, it is widely used in JDK, dubbo, SpringBoot and Spring Cloud. SPI mechanism is also used in Druid to load all configured filters.

    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. Configuration related to processing driver

There is nothing special about this. It only generates driver instances according to different database drivers, and makes special treatment for MockDriver and 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. Initialization verification

This is all kinds of database verification, such as Oracle version and simple query verification, db2 query verification, etc

4. Initialize exception storage

It is used to set the "exceptionSorter" attribute, which guarantees the stability of Druid connection pool and is used to handle major unrecoverable exceptions. It is an interface. Different databases have different implementation classes. Here, the corresponding "exceptionSorter" is generated according to different driver types.

5. Initialize {validConnectionChecker

Initialize validConnectionChecker according to different databases. This class is very important to detect the availability of connections in the connection pool. If the connection is not available, close it; If available, continue to put it back into the connection pool.

6. Check connection query

Verify whether the connection query sql is executed correctly.

5, Set dataSourceStat and initialize the array of dataSourceStat holder s

Generate dataSourceStat: judge whether dataSourceStat adopts global dataSourceStat. If so, set the global property of dataSourceStat, and then set the database type. If not, create a new dataSourceStat.

Set the reset flag of {dataSourceStat

Create a # DruidConnectionHolder array, which is the array of # connections of all connections, the array of expelled connections, # evictConnections, and the array of surviving connections. The length of the three arrays is the maximum number of connections, # maxActive.

            // dataSourceStat Is it adopted Global. yes dataSourceStat conduct set.  initialization holder Array of
            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];

 

6, Initialize

Perform asynchronous initialization or synchronous initialization according to the configuration:

If it is asynchronous initialization, call {submitcreatask() for processing.

If it is not initialized asynchronously, a physical Connection is created. Use poolingcount < initialsize to control the number of connections created. However, in the default initialization process, if it is not specified through other configuration parameters, this condition will not be triggered. This can be regarded as the lazy loading of DruidDataSource. Physical connections will be created only when connections are really needed.

            // Determine whether to perform asynchronous initialization
            if (createScheduler != null && asyncInit) {
                //  If asynchronously initialized, the call passes submitCreateTask conduct
                for (int i = 0; i < initialSize; ++i) {
                    submitCreateTask(true);
                }
            } else if (!asyncInit) {
                //  If poolingCount < initialSize,Create a physical connection. However, in the default initialization process, if it is not specified through other configuration parameters, this condition will not be triggered, which can be regarded as DruidDataSource Lazy loading, only really needed Connection The physical connection will be created only when.
                // 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();
                }
            }

 

7, Thread creation

After the thread pool initialization is completed, call createAndLogThread() to create a log thread. However, the condition timeBetweenLogStatsMillis of this thread is greater than 0. If this parameter is not configured, the log thread will not be created.

Call the createandstartcreaterthread() method to create a thread to create a connection, and assign the created thread to the variable createConnectionThread

Create a DestroyTask object, create a destroyConnectionThread thread and start, and assign the created thread to the variable destroyConnectionThread.

If keepAlive is true, you also need to call the submitcreatask method to fill the connection into the minIdle to ensure that the idle connection is available.

            // Create a log thread, but the condition of this thread timeBetweenLogStatsMillis Greater than 0. If this parameter is not configured, the log thread will not be created.
            createAndLogThread();
            // Create a CreateConnectionThread Object and start. initialize variable createConnectionThread. 
            createAndStartCreatorThread();
            // establish DestroyTask Object. Create at the same time DestroyConnectionThread Thread, and start,initialization destroyConnectionThread. 
            createAndStartDestroyThread();

            // Ensure that both methods are implemented
            initedLatch.await();
            init = true;

            initedTime = new Date();

            // register registerMbean
            registerMbean();

            if (connectError != null && poolingCount == 0) {
                throw connectError;
            }

            //  If keepAlive by true,Also call submitCreateTask Method to populate the connection to minIdle. Ensure that idle connections are available.
            if (keepAlive) {
                // async fill to minIdle
                if (createScheduler != null) {
                    for (int i = 0; i < minIdle; ++i) {
                        submitCreateTask(true);
                    }
                } else {
                    this.emptySignal();
                }
            }

 

8, Initialization complete

After all logic processing is completed, in finally, the initialization completion flag inited will be set to true, and the lock will be released at the same time. Finally, judge the INFO status of init and log, and print a log of init completion.

finally {
            // modify inited by true,And unlock.
            inited = true;
            lock.unlock();

            // judge init And log INFO Status, print one init Completed log.
            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);
            }
        }

9, init process summary

In the init process, the DruidDataSource is initialized. In order to prevent the init operation in the multi-threaded concurrency scenario, the Double Check method is adopted, combined with the ReentrentLock judgment twice. The detailed flow chart is as follows:

        

 

Tags: Druid

Posted by wakenbake on Wed, 11 May 2022 18:12:33 +0300