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

前言

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

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
druid数据库连接池是一个开源的Java数据库连接池。它通过提供高效的、可靠的连接管理和监控功能,帮助开发人员更好地管理数据库连接并提高系统性能。使用druid连接池,你可以实现数据库连接的复用、连接的自动管理、连接的监控和统计等功能。 在使用druid连接池时,你需要按照以下步骤进行配置和使用: 1. 添加druid依赖:在项目的pom.xml文件中添加druid的依赖。 2. 配置数据源:在项目的配置文件中配置druid数据源的相关信息,包括数据库的URL、用户名、密码等。 3. 初始化数据源:在项目启动时,通过编程方式初始化druid数据源,并将其注册到JNDI或者使用Spring等框架进行管理。 4. 获取连接:通过druid数据源的getConnection()方法获取数据库连接。 5. 使用连接:使用获取到的数据库连接执行SQL语句,进行数据库操作。 6. 关闭连接:在使用完数据库连接后,务必通过调用connection.close()方法将连接释放回连接池。 除了基本的数据库连接管理功能,druid还提供了一些高级特性,例如: - 连接池的监控功能:可以通过配置druid的监控管理页面,实时查看连接池的状态、活动连接数、慢SQL等信息。 - SQL防火墙功能:可以对SQL进行实时监控和审计,以防止SQL注入等安全问题。 - 配置参数的动态修改:可以通过监控管理页面或者JMX接口动态修改连接池的配置参数,而无需重启应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河海哥yyds

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值