【Druid源码阅读】1. 初识Druid

目录

1. 简介

2. 准备工作

3. Demo

4. 获取Connection解读


1. 简介

最早接触Druid 还是使用 Spring 的 xml 配置时代,一堆配置参数。

然后就是SpringBoot,使用配置类集成。

Druid 是一款很优秀的连接池,之前一直没有读过源码,只是项目搭建时集成一下,现在有机会,静下心来,随我一起看看 Druid 值得我们学习和借鉴的细节吧。

2. 准备工作

先到 github 上把源码 clone 下来。

https://github.com/alibaba/druid.git 

然后,把 1.2.8 tag 代码 checkout 下来,此次分析基于 1.2.8 版本。

运行 maven 命令编译代码

mvn clean install -DskipTests

3. Demo

去年时在搭建一个新服务时,在开发服务器上出现了连接 MySQL 报错的问题。试了很多方法,一直找不到问题根源。老大这时提出了一个思路,最小化实现一个 Demo,放到服务器上能否复现这个错误。这应该就是“控制变量法”吧,缩小排查范围。

众所周知,Druid 最重要的一个类就是 DruidDataSource,源码中也有相关的单测类:src/test/java/com/alibaba/druid/pool/demo/Demo0.java

找到它,把里面的参数改成自己的 MySQL 参数,并增加SQL查询并打印:

String sql = "select * from user limit 2;";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet result = pstmt.executeQuery();
while (result.next()) {
    String account = result.getString("account");
    String userId = result.getString("userId");
    System.out.println("account = " + account + ", userId = " + userId);
    System.out.println("****************");
}

还需要把 maxPoolSize 设置最小为10,否则运行时会报错(maxPoolSize小于initialSize)。

开启 Debug 模式,逐步探索 Druid 是如何获取到数据库连接的。

4. 获取数据库连接

上面 Demo 最核心的一行代码,就是获取数据库连接:

Connection conn = dataSource.getConnection();

跟到 DruidDataSource 类中

@Override
public DruidPooledConnection getConnection() throws SQLException {
    return getConnection(maxWait);
}

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
    init();

    if (filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(this);
        return filterChain.dataSource_connect(this, maxWaitMillis);
    } else {
        return getConnectionDirect(maxWaitMillis);
    }
}

可以看到 getConnection(long) 方法分了两部分:

  1. 初始化方法 init;
  2. 获取数据库连接;

我们先粗略看看初始化方法 init()。

大概分为如下几大块(后面有源码,可以开2个tab页对比着看):

  1. 判断是否已经初始化(只有第一次获取连接时才真正执行初始化方法);
  2. 使用继承自父类 DruidAbstractDataSource 的可重入锁 ReentrantLock lock 加锁(强制串行化,避免线程安全问题);
  3. 获取到锁后,再次验证是否已经初始化;
  4. 初始化 DruidDataSource 类中属性值,包括线程栈信息、id、一系列对象属性更新器、url、过滤器、数据库类型等;
  5. 连接池配置校验(最大活跃、初始化、最小空闲等);
  6. 以ServiceLoader方式初始化过滤器: initFromSPIServiceLoader();
  7. 获取驱动 Driver:resolveDriver();
  8. 根据数据库类型初始化检查:initCheck();
  9. 初始化 ExceptionSorter(剔除“不可用连接”的机制):initExceptionSorter();
  10. 根据数据库类型初始化合法连接校验器:initValidConnectionChecker();
  11. 校验 validationQuery 是否正确设置(默认为 “SELECT 1”):validationQueryCheck();
  12. 实例化 JdbcDataSourceStat 用于统计信息;
  13. 实例化3个数组,用于存放所有连接、将要被剔除连接、保活连接;
  14. 初始化连接,数量为设置的初始连接数,并存放到数组中;(真正创建连接在这一步中)
  15. 创建并运行日志线程:createAndLogThread();
  16. 创建并运行创建连接线程:createAndStartCreatorThread();
  17. 创建并运行销毁连接线程:createAndStartDestroyThread();
  18. 注册 MBean:registerMbean();
  19. 解锁,打印日志;

其中,需要重点关注的有2、9、14、16、17。

其中,第2点,加锁代码如下:

final ReentrantLock lock = this.lock;
try {
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    throw new SQLException("interrupt", e);
}

父类 DruidAbstractDataSource 构造方法中,初始化了可重入锁,以及2个条件(非空、为空);

public DruidAbstractDataSource(boolean lockFair){
    lock = new ReentrantLock(lockFair);

    notEmpty = lock.newCondition();
    empty = lock.newCondition();
}

默认创建的是 非公平锁,当然也可以自行指定为公平锁;

    public DruidDataSource(){
        this(false);
    }

    public DruidDataSource(boolean fairLock){
        super(fairLock);

        configFromPropety(System.getProperties());
    }

这2个条件在创建和销毁连接时起到关键作用,这个后续再分析。

最后,把 init 方法源码贴在这里,方便大家不用打开开发工具就可以查看:

public void init() throws SQLException {
    // 1. 判断是否已经初始化
    if (inited) {
        return;
    }

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

    // 2. 加锁
    final ReentrantLock lock = this.lock;
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        throw new SQLException("interrupt", e);
    }

    boolean init = false;
    try {
        // 3. 获取到锁后,再次验证是否已经初始化;
        if (inited) {
            return;
        }

        // 4. 初始化 DruidDataSource 类中属性值
        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 (JdbcUtils.isMysqlDbType(dbType)) {
            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");
            }
        }

        // 5. 连接池配置校验
        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");
        }

        if (this.driverClass != null) {
            this.driverClass = driverClass.trim();
        }

        // 6. 以ServiceLoader方式初始化过滤器
        initFromSPIServiceLoader();

        // 7. 获取驱动 Driver
        resolveDriver();

        // 8. 根据数据库类型初始化检查
        initCheck();

        // 9. 初始化 ExceptionSorter(剔除“不可用连接”的机制)
        initExceptionSorter();

        // 10. 根据数据库类型初始化合法连接校验器
        initValidConnectionChecker();

        // 11. 校验 validationQuery 是否正确设置
        validationQueryCheck();

        // 12. 实例化 JdbcDataSourceStat 用于统计信息
        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);

        // 13. 实例化3个数组,用于存放所有连接、将要被剔除连接、保活连接;
        connections = new DruidConnectionHolder[maxActive];
        evictConnections = new DruidConnectionHolder[maxActive];
        keepAliveConnections = new DruidConnectionHolder[maxActive];

        SQLException connectError = null;

        // 14. 初始化连接
        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();
            }
        }

        // 15. 创建并运行日志线程
        createAndLogThread();

        // 16. 创建并运行创建连接线程
        createAndStartCreatorThread();

        // 17. 创建并运行销毁连接线程
        createAndStartDestroyThread();

        initedLatch.await();
        init = true;

        initedTime = new Date();

        // 18. 注册 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 {
        // 19. 解锁,打印日志;
        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);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值