Java开源数据库连接池比较(c3p0,dbcp,druid)

DBCP DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用可以设置最大和最小连接,连接等待时间等,基本功能都有,此连接池的持续运行的稳定性还是可以,不过速度稍慢,在大并发量的压力下稳定性有所下降,此外不提供连接池监控
C3P0 C3P0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。连接池可以设置最大和最小连接,连接等待时间等,基本功能都有,连接池的持续运行的稳定性相当不错,在大并发量的压力下稳定性也有一定保证,此外不提供连接池监控。

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

#指定连接属性



在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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我会回答你的问题,以下是常用数据库连接池 DBCPc3p0Druid 的配置说明: 1. DBCP (Apache Commons DBCP) DBCP 是 Apache Commons 项目中的一个子项目,它是一个纯 Java 实现的数据库连接池DBCP 的使用非常简单,只需要引入相关的 jar 包即可。以下是 DBCP 的配置说明: ``` # 最大连接数 maxActive=100 # 最大空闲连接数 maxIdle=30 # 最小空闲连接数 minIdle=10 # 获取连接时的最大等待时间,单位为毫秒 maxWait=10000 # 是否开启自动回收空闲连接的线程 removeAbandoned=true # 回收超时时间,单位为秒 removeAbandonedTimeout=180 # 是否开启缓存 PreparedStatement,提高性能 poolPreparedStatements=true # 缓存 PreparedStatement 的最大数量 maxOpenPreparedStatements=100 ``` 2. c3p0 c3p0 是一个开源的 JDBC 数据库连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 的 Connection 和 Statement 缓存以及 JDBC4 的自动化管理。以下是 c3p0 的配置说明: ``` # 最大连接数 c3p0.maxPoolSize=100 # 最小连接数 c3p0.minPoolSize=10 # 初始化连接数 c3p0.initialPoolSize=10 # 获取连接时的最大等待时间,单位为毫秒 c3p0.checkoutTimeout=10000 # 是否自动回收超时连接 c3p0.autoCommitOnClose=true # 是否开启自动回收空闲连接的线程 c3p0.idleConnectionTestPeriod=60 # 回收超时时间,单位为秒 c3p0.maxIdleTime=1800 # 是否开启缓存 PreparedStatement,提高性能 c3p0.cachePreparedStatements=true # 缓存 PreparedStatement 的最大数量 c3p0.maxStatements=100 ``` 3. Druid Druid 是阿里巴巴开源的一个高性能、可扩展、功能强大的数据库连接池。它主要提供了以下功能:监控统计、防御 SQL 注入、批量处理、数据源加密、日志记录等。以下是 Druid 的配置说明: ``` # 最大连接数 druid.maxActive=100 # 最大空闲连接数 druid.maxIdle=30 # 最小空闲连接数 druid.minIdle=10 # 获取连接时的最大等待时间,单位为毫秒 druid.maxWait=10000 # 是否开启自动回收空闲连接的线程 druid.removeAbandoned=true # 回收超时时间,单位为秒 druid.removeAbandonedTimeout=180 # 是否开启缓存 PreparedStatement,提高性能 druid.poolPreparedStatements=true # 缓存 PreparedStatement 的最大数量 druid.maxOpenPreparedStatements=100 # 是否开启 SQL 执行监控 druid.stat=true # 是否开启防御 SQL 注入功能 druid.filters=stat,wall,log4j ``` 以上就是常用数据库连接池 DBCPc3p0Druid 的配置说明。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值