Druid连接池初始化简单分析(一)

6,862 阅读4分钟
微信搜索 爪哇根据地,一起学习一起进步!

Druid简介

连接池的出现解决了高并发场景下,为每次请求创建数据库连接时程序的性能消耗,以及请求响应时间的增加。之前使用过C3P0连接池,后来关注了Druid连接池,一个阿里巴巴数据库事业部出品,号称为监控而生的数据库连接池。与其他连接池相比,提供了丰富的,多维度的数据监控。

Druid git 源码地址:github.com/alibaba/dru…


Druid的初始化:

常用的属性名词说明:

url                            :数据库地址,通过前缀指定驱动类型。
username                       :数据库用户名
password                       :数据库密码
initialSize                    :连接池初始化大小
maxActive                      :连接池最大连接数
maxWait                        :获取连接最大等待时间
testOnBorrow                   :获取连接时是否检测连接是否有效
testOnReturn                   :回收连接时是否检测连接是否有效
testWhileIdle                  :获取连接时是否检测连接连接空闲时间超过
                                 timeBetweenEvictionRunsMillis,超过则检测连接
timeBetweenEvictionRunsMillis  :DestroyConnectionThread线程(定时检测连接有效性)
                                 执行间隔(Sleep控制)
minEvictableIdleTimeMillis     :连接再线程池中最小存活时间
removeAbandoned                :是否开启线程活动时间超过removeAbandonedTimeout,进行丢弃
removeAbandonedTimeout         :活动线程最大存活时间,超过直接丢弃(单位分钟)。
logAbandoned                   :关闭abandon连接是否打印日志。
filters                        :需要启用Druid的过滤器。
connectionInitSqls             :连接时需要设置的mysql参数,例:set names utf8mb4;asyncInit                      :是否异步初始化连接池
createScheduler                :初始化连接的线程池
validationQuery                :创建连接校验连接是否有效执行的sql语句
keepAlive                      :是否创建空闲连接
minIdle                        :最小空闲连接数

在Spring中可以配置数据源,并在init-method属性直接调用init方法。

<bean name="readDataSource1" class="com.alibaba.druid.pool.DruidDataSource"  
 init-method="init" destroy-method="close">

如果没有手动调用初始化,当第一次调用DataSource的getConnection方法时也会触发初始化逻辑完成初始化。

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

DruidDataSource初始化大体做了几件事:

1.处理自定义的参数值。根据jdbcUrl判断数据库类型(Mysql或Oracle)、检查参数是否合法、初始化所配置的过滤器以及加载驱动。

2.初始化检测器,如用于连接是否有效的检测器,和用于查询语句是有有效的检测器。

3.实例化了一个数据源统计对象JdbcDataSourceStat。Druid连接池以监控闻名,JdbcDataSourceStat则是数据的存储中心!

4.初始化三个数组,分别存放数据库连接,丢弃的连接,已经检测后仍然存活的连接,并初始化数据库连接。初始化三个线程,分别为日志记录线程,生成数据库连接,和检测连接是否超时。

DruidDataSource初始化几个关键点:

初始化连接池

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

当property配置了createScheduler(线程池) 、asyncInit(boolean),并且asyncInit值为true,则进行异步初始化,反之阻塞主线程直至初始化完成。poolingCount为当前线池大小。

private void submitCreateTask(boolean initTask) {
    createTaskCount++;
    //创建生成连接的Runnable任务
    CreateConnectionTask task = new CreateConnectionTask(initTask);
    if (createTasks == null) {
        //long类型数组用于存放Runnable任务
        createTasks = new long[8];
    }
    boolean putted = false;
    for (int i = 0; i < createTasks.length; ++i) {
        if (createTasks[i] == 0) {
            createTasks[i] = task.taskId;
            putted = true;
            break;
        }
    }
    if (!putted) {
        //当createTasks数组满时,扩容为之前数组大小的1.5倍。
        long[] array = new long[createTasks.length * 3 / 2];
        System.arraycopy(createTasks, 0, array, 0, createTasks.length);
        array[createTasks.length] = task.taskId;
        createTasks = array;
    }
    //丢进线程池执行
    this.createSchedulerFuture = createScheduler.submit(task);
}

方法createPhysicalConnection()返回一个持有引用实际连接的

PhysicalConnectionInfo对象,根据配置的property,创建数据库连接。创建好后对连接进行初始化(是否自动commit,设置事务隔离等级,预执行
connectionInitSqls中的数据量设置语句),有效性检验校验,执行配置好的sql语句。

将返回的PhysicalConnetionInfo和DruidDataSource  生成 
DruidConnectionHolder对象(连接池实际操作的对象),放入连接池数组中直到连接池大小等于initialSize。

asyncInit 配置为false时,通过一个while循环创建连接,核心代码如下

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

逻辑与线程池模式一致。


到此完成了Druid线程池,常规参数配置及初始化规定数量连接数。剩下还有三个线程的创建,留到下章继续分析


                                                           

                                                         (扫码关注公众号)



注:基于水平限制,文档只是对于自己的提升,有出入请指教。(未经授权禁转)