必威体育Betway必威体育官网
当前位置:首页 > IT技术

学习一下强大的druid,看看druid 连接池部分的源码-创建,使用,销毁。

时间:2019-08-08 11:14:32来源:IT技术作者:seo实验室小编阅读:66次「手机版」
 

连接池

前言

druid是阿里爸爸的开源数据库连接池,据说其性能算是位于领先的水平,从连接的创建和销毁这个性能方面优于其它连接池,但是觉得和hikariCP,的速度比起来还是差点。但是两者各有好处,一个是扩展性比较优秀功能比较全,一个是速度比较块。以下是性能对照图:

图片出处:https://github.com/brettwooldridge/HikariCP

这里写图片描述

图片出处:druid Github

这里写图片描述

图片出处:http://blog.didispace.com/java-datasource-pool-compare/

这里写图片描述

这里写图片描述

于是便引起了我这个菜鸟的好奇心,由于druid功能强大,我是菜鸟,就看看连接池的一些内容吧。

小试牛刀-使用

配置:

配置列表,其兼容DBCP的各项配置,但是有些许的语义区别。以下是我简单的入门配置。

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14_customer
username=root
password=

#初始链接数,在连接池被创建的时候初始化的连接数
initSize=20
#最大连接池数量
maxActive=20
#最小连接池数量
Minidle=5

#超时等待时间
maxWait=60000

#指定连接属性
connectionProperties=useSSL=true;rewriteBATchedstatements=true

在java中简单使用:

package Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class Druid {
    private static DataSource dataSource;

    static {
        try {
            InputStream inputStream = DBCP.class.getClassLoader().getResourceAsStream("dbconfig.properties");
            Properties properties = new Properties();
            properties.load(inputStream);
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printstacktrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public static void release(Connection conn, Statement st, ResultSet rs) {
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

挺简单的没啥好说的

看看源码

重要变量

DruidAbstractDataSource.class

//重入锁
protected ReentrantLock lock;
//empty,notEmpty条件,负责线程的阻塞唤醒
protected Condition notEmpty;
protected Condition empty;

DruidSource.class

//存放线程的底层数组
private volatile DruidConnectionHolder[] connections;

工厂函数创建datasource

public static DataSource createDataSource(Map properties) throws Exception {
    DruidDataSource dataSource = new DruidDataSource();
    //从properties中配置datasource
    config(dataSource, properties);
    return dataSource;
}

在获取连接的过程进行初始化

public DruidpooledConnection getConnection(long maxWaitMillis) throws SQLException {
    this.init();
    if (this.filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(this);
        return filterChain.dataSource_connect(this, maxWaitMillis);
    } else {
        return this.getConnectionDirect(maxWaitMillis);
    }
}

初始化的方法中有这样的几个线程启动,一个是create一个是destroy

 this.createAndLogThread();
 this.createAndStartCreatorThread();
 this.createAndStartDestroyThread();

lock、condition、两个线程-创建/销毁connection

从源码我们可以看出druid连接池是基于ReetrantLock的,它有两个condition,empty和notEmpty,一个是监控连接池是否为空一个是监控连接池不为空的。这是连接池的核心部分,让我们一探究竟。注意,这两个条件condition都是谁用?等待为空的线程是创建condition的线程,等待不为空的是getConnection。

创建线程:

com.alibaba.druid.pool.DruidDataSource.CreateConnectionThread

try {
    //等待为空的变量,依据这个变量来进行condition:empty的阻塞(awiait)
    boolean emptyWait = true;
    if (DruidDataSource.this.createERROR != null && DruidDataSource.this.poolingCount == 0 && !discardChanged) {
    emptyWait = false;
    }

    if (emptyWait && DruidDataSource.this.asyncInit && DruidDataSource.this.createCount < (long)DruidDataSource.this.initialSize) {
    emptyWait = false;
    }

    if (emptyWait) {
    if (DruidDataSource.this.poolingCount >= DruidDataSource.this.notEmptyWaitThreadCount && (!DruidDataSource.this.keepAlive || DruidDataSource.this.activeCount + DruidDataSource.this.poolingCount >= DruidDataSource.this.minIdle)) {
       DruidDataSource.this.empty.await();
    }

    if (DruidDataSource.this.activeCount + DruidDataSource.this.poolingCount >= DruidDataSource.this.maxActive) {
       DruidDataSource.this.empty.await();
       continue;
    }
}

等待空的两个if条件

  • 池中的连接数量>=等待不为空的线程数量.意思就是说你请求一个连接我连接池里面有,这个时候就不用创建连接了,连接的线程就await,阻塞
  • 活跃的数量+池中的数量>=连接池总数量,意思就是说连接池数量达到了池的上限,这个时候就不能再创建连接了。
if (connection != null) {
    // 往线程中put进一个连接connection
    boolean result = DruidDataSource.this.put(connection);
    if (!result) {
        JdbcUtils.close(connection.getPhysicalConnection());
        DruidDataSource.LOG.info("put physical connection to pool failed.");
    }

    errorCount = 0;
}

获取线程

获取线程的逻辑主要是它来实现的,让我们进一步探究一下。

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
    this.init();
    if (this.filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(this);
        return filterChain.dataSource_connect(this, maxWaitMillis);
    } else {
        return this.getConnectionDirect(maxWaitMillis);
    }
}

最后的弹出连接是这个方法:

DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
    try {
        while(this.poolingCount == 0) {
            //连接池空了,唤醒生产者来创建连接
            this.emptysignal();
            if (this.failFast && this.failContinuous.get()) {
                throw new DataSourceNotAvailableException(this.createError);
            }

            ++this.notEmptyWaitThreadCount;
            if (this.notEmptyWaitThreadCount > this.notEmptyWaitThreadPeak) {
                this.notEmptyWaitThreadPeak = this.notEmptyWaitThreadCount;
            }

            //阻塞了一个,数量也就-1
            try {
                this.notEmpty.await();
            } finally {
                --this.notEmptyWaitThreadCount;
            }

            ++this.notEmptyWaitCount;
            if (!this.enable) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new DataSourcedisableException();
            }
        }
    } catch (InterruptedException var5) {
        this.notEmpty.signal();
        ++this.notEmptySignalCount;
        throw var5;
    }
    //减少池中的连接计数,下面的部分就是取连接的操作
    this.decrementPoolingCount();
    DruidConnectionHolder last = this.connections[this.poolingCount];
    this.connections[this.poolingCount] = null;
    return last;
}

两个有用的字段:notEmptyWaitThreadCount这个字段也就是消费者数量,notEmptyWaitCount维护的就是正在等待的消费者数量

这让我就突然想起了生产者和消费者的思想,创建线程是生产者,获取线程是消费者,不难发现notEmpty这个Condition是用在了它上面。

销毁线程

public class DestroyTask implements Runnable {
    public DestroyTask() {
    }

    public void run() {
        DruidDataSource.this.shrink(true, DruidDataSource.this.keepAlive);
        if (DruidDataSource.this.isRemoveAbandoned()) {
            DruidDataSource.this.removeAbandoned();
        }

    }
}

shrink的关键就在这,其主要功能是收缩就是空闲的太多了要进行回收了。

if (evictCount > 0) {
    for(checkCount = 0; checkCount < evictCount; ++checkCount) {
        holer = this.evictConnections[checkCount];
        connection = holer.getConnection();
        JdbcUtils.close(connection);
        destroyCountUpdater.incrementAndGet(this);
    }

    Arrays.fill(this.evictConnections, (Object)null);
}

总结

这次的小blog只是就简单的实现原理看了看,可以看出连接池是生产者消费者这种形式运作的,主要是两个线程来保证连接池,其中很多细节未涉及,笔者能力有限。如果有机会还想学习一下监控,据说是通过责任链来实现的,责任链我不懂,就没有深入。总的来说,旨在入个门把。

深入学习的参考链接

性能分析对比:

http://developer.51cto.com/art/201807/579402.htm

源码:

https://www.jianshu.com/p/549bfacebb38

https://www.jianshu.com/p/16a646a4eccd

配置表:

https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

相关阅读

springboot 2.0 使用Hikari连接池(号称java平台最快的,

1.springboot 2.0 默认连接池就是Hikari了,所以引用parents后不用专门加依赖 2.贴我自己的配置(时间单位都是毫秒) # jdbc_config

C3P0连接池参数配置

<!--acquireIncrement:链接用完了自动增量3个。 --> <property name="acquireIncrement">3</property> <!--acquireR

分享到:

栏目导航

推荐阅读

热门阅读