Druid源码阅读系列(一)

今天我们一起来看下Druid。

准备工作

先将Druid的源码down下来,传送门:https://github.com/alibaba/druid/tree/1.2.8 ,如果是国内用户可能会比较慢,我反正是这样,推荐使用国内镜像

git clone https://gitclone.com/github.com/alibaba/druid.git //域名中间加上gitclone.com

mvn clean install -DskipTests

开始阅读

Druid作为一个数据库连接池框架,提供了很强大的性能和监控。作为一个数据库框架其实核心就是对数据库连接的各种操作,在连接上进行各种封装。

所以我们先从pool包的 DruidDataSource 类开始。通过构造方法可以看出,连接池默认使用的非公平锁策略,因为效率会更高。

在这里插入图片描述

这里可以看到有一个super方法,是父类DruidAbstractDataSource 构造器里实现的,具体做了什么呢?其实很简单,就是初始化了一个锁,和两个condition。那后面其实会用到锁去控制资源的访问。

在这里插入图片描述

然后下面configFromPropety()方法看名字就知道是对设置的属性进行赋值,毕竟这个类里面定义了那么多成员变量,我们进去确认下。

在这里插入图片描述

和我猜的一样,只是这种两个中括号的格式和我们日常代码有点不太一样,第一眼看着有点奇怪。

我们通过框架的测试用例可以看出类DruidDataSource 构造之后,调用的入口其实是init()方法。

我们来详细看下init()这个方法:

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

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

            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 (this.driverClass != null) {
                this.driverClass = driverClass.trim();
            }

            //初始化SPI相关配置
            initFromSPIServiceLoader();

            //根据对应驱动类创建驱动,里面增加了对MockDriver的创建
            resolveDriver();
			
            //对oracle和db2的一些常规错误进行了校验
            initCheck();

            //分类初始化驱动
            initExceptionSorter();
            
            //初始化连接有效性验证器
            initValidConnectionChecker();
            
            //验证查询
            validationQueryCheck();

            //判断是否适用全局DataSourceStat,如果不是应该后续什么地方可以自定义datasource
            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];

            SQLException connectError = null;

            //分同步和异步两种情况创建连接任务
            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();

            initedLatch.await();
            init = true;

            initedTime = new Date();
            
            //注册Mbean
            registerMbean();

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

            if (keepAlive) {
                // async fill to minIdle
                if (createScheduler != null) {
                    for (int i = 0; i < minIdle; ++i) {
                        submitCreateTask(true);
                    }
                } else {
                    this.emptySignal();
                }
            }

        } catch (SQLException e) {
            LOG.error("{dataSource-" + this.getID() + "} init error", e);
            throw e;
        } catch (InterruptedException e) {
            throw new SQLException(e.getMessage(), e);
        } catch (RuntimeException e){
            LOG.error("{dataSource-" + this.getID() + "} init error", e);
            throw e;
        } catch (Error e){
            LOG.error("{dataSource-" + this.getID() + "} init error", e);
            throw e;

        } finally {
            inited = true;
            lock.unlock();

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

上面第九行代码,看着很奇怪,又没有变量去接收,那执行它有啥用呢?其实就是为了执行对应类里面静态代码块的驱动注册而已。当Thread-1执行init()方法时,这时候会锁住DruidDriver,然后一直持有DriverManage,因为需要DriverManage注册驱动,如果Thread-2这时候执行init()方法,若Thread-2被interrupted,则会抛出SQLException异常,会调用DriverManager静态代码块里的loadInitialDrivers()方法,会一直持有DruidDriver,锁住DriverManager,两者互相等待造成死锁。

然后官方的解决方案也很简单,既然是因为初始化静态代码块的时候出现死锁,那就在加锁之前初始化完成,后续即使在加载类也不会加载静态代码块了。

后面就是一些基础值的获取和各种错误的检测,各种配置和驱动设置,比如SPI和各个数据库JDBC驱动等,一些关键地方我都有写注释,然后这个类最核心的地方应该是在创建连接那个地方,有很多复杂的判断和各种计数,这块我现在也没太看懂,等明天调试下在记录下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值