Analyse du code source du druide

Table des matières

configuration

DruidDataSourceAutoConfigure

nouveau DruidDataSourceWrapper()

Constructeur DruidDataSource

méthode d'initialisation

 getConnection()

résumé


configuration

DruidDataSourceAutoConfigure

// 配置类
@Configuration
// 有DruidDataSource类时生效
@ConditionalOnClass(DruidDataSource.class)
// 在DataSourceAutoConfiguration类之前进行配置
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
// 读取配置
// DruidStatProperties主要是监控网站配置和网站计数过滤器配置
// DataSourceProperties 主要是数据库配置
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
// 导入四个不同的配置类
// DruidSpringAopConfiguration用于启用对 Druid 数据库连接池的 AOP 支持
// DruidStatViewServletConfiguration 根据配置开启Druid 连接池的监控统计功能
// DruidWebStatFilterConfiguration 根据配置开启Web和Druid数据源之间的管理关联监控统计
// DruidFilterConfiguration 根据配置注入各种filter的bean
@Import({DruidSpringAopConfiguration.class,
        DruidStatViewServletConfiguration.class,
        DruidWebStatFilterConfiguration.class,
        DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
    // 初始化时执行DruidDataSource的init方法
    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        // 返回DruidDataSourceWrapper实例
        return new DruidDataSourceWrapper();
    }
}

nouveau DruidDataSourceWrapper()

// 读spring.datasource.druid开头的配置
@ConfigurationProperties("spring.datasource.druid")
@Override
    public void afterPropertiesSet() throws Exception {
        //如果不存在spring.datasource.druid的配置,就用'spring.datasource'配置
        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());
        }
    }

Constructeur DruidDataSource

DruidDataSource est la classe parente de DruidDataSourceWrapper

Appelez la méthode configFromPropety lors de l'instanciation de DruidDataSource

La méthode consiste principalement à lire la configuration de System.getProperties(), puis à attribuer la valeur, le code est simple et ne sera pas publié.

Il convient de noter que la méthode configFromPropety est exécutée en premier, puis la méthode set est exécutée. L'ordre de configuration est que le fichier de configuration est prioritaire.

méthode d'initialisation

Comme mentionné ci-dessus, la méthode init de DruidDataSource sera exécutée après l'instanciation du bean de dataSource. Voyons ce que fait la méthode init

 // 判断是否被初始化过,是则直接退出  
  if (inited) {
      return;
  }
        DruidDriver.getInstance();
        
        final ReentrantLock lock = this.lock;
        try {
            // 尝试获取锁,如果获取失败则等待锁被释放,直到被其他线程打断或者当前线程获取到锁为止
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }
            // 为数据源分配id
            this.id = DruidDriver.createDataSourceId();
            if (this.id > 1) {
                // 更新一些JMX的监控指标
                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();
                // 以下两个方法是解析url,从中找到配置去配置druidDataSource,包括filter、connectTimeout等
                initFromWrapDriverUrl();
                initFromUrlOrProperties();
            }
// 中间省略一些赋值操作
…… ……
// 从SPI ServiceLoader加载过滤器
initFromSPIServiceLoader()
// 给driver赋值
resolveDriver();
// 对dbType为Oracle、db2、mysql的做出相应处理
initCheck();
            // 根据driverClassName给exceptionSorter赋值,异常分类器可以帮助我们对不同类型的 SQL 异常进行分类和管理,例如识别数据库链接超时、死锁等情况,并进行相应的处理,避免出现严重的数据问题。
            initExceptionSorter();
            // 根据driverClassName给validConnectionChecker赋值,通过合法连接检测器,我们可以过滤掉已关闭或已失效的数据库连接,以避免在数据库操作时出现异常情况。
            initValidConnectionChecker();
            // 在 Druid 数据源内部,默认会使用 Validation Query 来检查每个数据库连接的有效性,以确保连接池中的所有连接都是可用的,从而提高数据库操作的效率和稳定性。
            validationQueryCheck();
         // 看是否配置全局数据源统计,dataSourceStat主要用于收集和记录数据源的性能指标和运行状态信息。通过 JdbcDataSourceStat 类的属性可以了解到数据源运行期间的各种指标信息,
         //并根据这些信息进行优化和调整,以提高数据源的性能和稳定性。同时,我们也可以通过 Druid 的 Web 控制台对数据源进行实时监控和管理,及时发现和解决问题。
         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);
            // 声明了三个类型均为 DruidConnectionHolder 的数组
            // connections 数组表示数据源中所有的有效连接,当客户端请求连接时,连接池会向该数组中获取一个空闲连接,供客户端使用。
            // evictConnections 数组表示要被释放的无效连接,当检查到连接无效时,连接池会将该连接放入该数组中,并在后续时间将这些无效连接关闭和移除,以保证连接池中只有有效可用的连接。
            // keepAliveConnections 数组表示连接池中的存活连接,当连接池对连接进行健康检查时,发现连接处于“存活”状态,则将其放置在该数组中继续使用。
            connections = new DruidConnectionHolder[maxActive];
            evictConnections = new DruidConnectionHolder[maxActive];
            keepAliveConnections = new DruidConnectionHolder[maxActive];
            // 判断创建连接池的调度程序是否为空和asyncInit参数
            // 满足条件则异步创建连接
            if (createScheduler != null && asyncInit) {
                for (int i = 0; i < initialSize; ++i) {
                    submitCreateTask(true);
                }
            } else if (!asyncInit) {
                // 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();
            // 该线程会监测连接池中的连接数量,如果连接数小于最小活跃连接数,则会异步地创建新的连接并添加到连接池中,以保证连接池中至少存在最小活跃连接数的连接
            createAndStartCreatorThread();
            // 该线程会监测连接池中的空闲连接数量,如果连接数超出了最大空闲连接数,则会异步地销毁多余的连接,以避免连接池中的无效连接过多导致性能下降
            createAndStartDestroyThread();
            // 保证createAndStartCreatorThread完成才继续往下走
            initedLatch.await();
// 用于向 MBean 服务器注册 Druid 数据源的监控信息
// MBean(管理 Bean)是 Java 管理扩展(Java Management Extension,JMX)的核心概念之一,它是一种管理和监测 Java 应用程序的标准化方式。
// 创建一个 MBeanServer 对象,然后调用该对象的 registerMBean() 方法将 Druid 数据源的状态信息和配置信息注册为一个 MBean,即可在 JMX 控制台上查看和管理
registerMbean();
            // 如果设置了keepalive,连接会自动保持活动状态,以减少重新创建连接和验证连接的消耗
            if (keepAlive) {
                // 分为同步创建和异步创建连接
                if (createScheduler != null) {
                    for (int i = 0; i < minIdle; ++i) {
                        submitCreateTask(true);
                    }
                } else {
                    this.emptySignal();
                }
            }

 getConnection()

Ce qui précède est principalement le réglage initial, et le suivant est l'opération d'obtention de la connexion.

    public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        // 生成bean时已经初始化过,不再初始化
        init();
        // 如果过滤器不为空,则获取连接时对连接做一系列处理
        // 如果没使用过滤器则世界获取连接
        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis);
        }
    }

 Recherchez la méthode getConnectionInternal dans

                // 如果设置最大等待时间并且大于0
                if (maxWait > 0) {
                    holder = pollLast(nanos);
                } else {
                    // 如果最大等待时间小于0
                    holder = takeLast();
                }
    DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
        try {    
            while (poolingCount == 0) {
                emptySignal(); // 唤醒init里创建连接线程CreateConnectionThread
                …… ……
                try {
                    notEmpty.await(); // 防止未初始化完成就往下走,初始化一个连接后唤醒这里
                } finally {
                    notEmptyWaitThreadCount--;
                }
                …… ……
            }
        } catch (InterruptedException ie) {
            …… ……
        }
        // poolingCount--
        decrementPoolingCount();
        // 取出链接并把当前位置的connections置空
        DruidConnectionHolder last = connections[poolingCount];
        connections[poolingCount] = null;

        return last;
    }

 Lorsqu'une connexion est utilisée, elle appellera la méthode close de DruidPooledConnection, puis appellera la méthode recycle de DruidDataSource, et enfin appellera la méthode putLast(holder, currentTimeMillis) pour remettre la connexion dans les connexions

résumé

Analysons brièvement ici d'abord, puis analysons des problèmes spécifiques à l'avenir

Guess you like

Origin blog.csdn.net/wai_58934/article/details/130991332