Tomcat的新一代连接池jdbc-pool及实现分析

前面的文章,我们分析了Tomcat中的dbcp连接池的原理及实现。

数据源连接池的原理及Tomcat中的应用

其实,Tomcat官方还提供了另一种pool的实现,即jdbc-pool。

jdbc-pool的定位,是做为DBCP Pool的替代,或至少是另一个选择

那我们为什么需要一个新的connection pool呢?

Tomcat官方文档是这个介绍的:

  1. DBCP 1.x是单线程的,为了保证线程安全,需要在对象的创建和返回时短时间的锁定整个池。

  2. 随着多线程并发的进行对象的获取和释放,性能反而会下降。在DBCP 2.x解决了这个问题

  3. DBCP 有60多个class, 而tomcat-jdbc-pool核心class只有8个。这样,只需要少量的修改就可以应对后续的需求

  4. Tomcat jdbc pool 的实现支持异步获取Connection, 而不需要增加额外的Thread

  5. 不会产生饥饿死锁。如果一个连接池是空的,并且有线程在等待连接,此时如果有连接返回,连接池会唤醒正确的线程。

此处,还增加了一些超过其它连接池的特性,例如:

  1. 支持高并发和多核心环境

  2. 动态的接口实现

  3. 支持自定义的interceptor,从而支持更多自定义的功能

更多的特性,请参见官方文档。

如何使用

看过我们上一篇文章的朋友,应该都了解DBCP 连接池在Tomcat中如何进行配置。想要从DBCP 切换到jdbc-pool,Tomcat官方也尽可能的提供了方便的方式,所有的属性配置基本都和DBCP一样,意义也一到。 所以,从DBCP到jdbc-pool,只需要配置factory属性,值设置为org.apache.tomcat.jdbc.pool.DataSourceFactory即可。

具体其内在的实现是什么样的呢?我们一起来看一下。

我们的数据仍采用和上一篇文章一样的配置,再增加Resourcefactory属性。

此时,我们会发现,在连接池的初始化时,会有下在的逻辑:

/**
     * Initialize the connection pool - called from the constructor
     * @param properties PoolProperties - properties used to initialize the pool with
     * @throws SQLException if initialization fails
     */
    protected void init(PoolConfiguration properties) throws SQLException {
        poolProperties = properties;
 
        //make sure the pool is properly configured
        checkPoolConfiguration(properties);
 
        //make space for 10 extra in case we flow over a bit
        busy = new LinkedBlockingQueue<>();
        //busy = new FairBlockingQueue<PooledConnection>();
        //make space for 10 extra in case we flow over a bit
        if (properties.isFairQueue()) {
            idle = new FairBlockingQueue<>();
            //idle = new MultiLockFairBlockingQueue<PooledConnection>();
            //idle = new LinkedTransferQueue<PooledConnection>();
            //idle = new ArrayBlockingQueue<PooledConnection>(properties.getMaxActive(),false);
        } else {
            idle = new LinkedBlockingQueue<>();
        }

从代码中我们发现,jdbc-pool使用了两个队列来存储连接,空闲的和忙碌的分别存放于busyidle 这两个LinkedBlockingQueue,当然,空闲队列会根据配置决定是否使用公平队列。

之后,会根据初始设置大小,来初始池中的连接

PooledConnection[] initialPool = new PooledConnection[poolProperties.getInitialSize()];
try {
    for (int i = 0; i < initialPool.length; i++) {
        initialPool[i] = this.borrowConnection(0, null, null); //don't wait, should be no contention
    } //for

borrowConnection方法中,对于初始和后续请求连接调用一样,会判断空闲队列的逻辑,以及配置的信息进行连接的创建

PooledConnection con = idle.poll();
 
        while (true) {
 
            if (size.get() < getPoolProperties().getMaxActive()) {
                //atomic duplicate check
                if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
                    //if we got here, two threads passed through the first if
                    size.decrementAndGet();
                } else {
                    //create a connection, we're below the limit
                    return createConnection(now, con, username, password);
                }
            } //end if

不满足配置的情况下,会从空闲队列获取

           con = idle.poll(timetowait, TimeUnit.MILLISECONDS);

由于空闲队列采用的是公平队列,不会出现饥饿等待的情况。 createConnection,就会在创建连接后直接进入busy队列。在finally块,又通过returnConnection,将连接释放,进入idle队列。

finally {
            //return the members as idle to the pool
            for (int i = 0; i < initialPool.length; i++) {
                if (initialPool[i] != null) {
                    try {this.returnConnection(initialPool[i]);}catch(Exception x){/*NOOP*/}
                } //end if
            } //for

再来看returnConnection方法,核心代码如下:

  protected void returnConnection(PooledConnection con) {
    if (busy.remove(con)) { // 重点看这里
    if (((idle.size()>=poolProperties.getMaxIdle()) && !poolProperties.isPoolSweeperEnabled())
    || (!idle.offer(con))) { //还有这里
    }
    release(con);
  }

以上,是两个池中最核心的方法逻辑,那么,还有一个问题是,在连接的关闭时,池是如何处理来进行连接的复用呢?

通过Connection的close方法调用时,关闭连接时的调用栈如下:

at org.apache.tomcat.dbcp.dbcp2.PoolableConnection.close(PoolableConnection.java:181)
  - locked <0xb59> (a org.apache.tomcat.dbcp.dbcp2.PoolableConnection)
  at org.apache.tomcat.dbcp.dbcp2.DelegatingConnection.closeInternal(DelegatingConnection.java:234)
  at org.apache.tomcat.dbcp.dbcp2.DelegatingConnection.close(DelegatingConnection.java:217)
  at org.apache.tomcat.dbcp.dbcp2.PoolingDataSource$PoolGuardConnectionWrapper.close(PoolingDataSource.java:228)

前面介绍jdbc-pool时,曾说过,他支持Interceptor。这里对于close方法的拦截,也是通过定义的Interceptor来实现的。

public class ProxyConnection extends JdbcInterceptor {
 
    protected PooledConnection connection = null;
 
    protected ConnectionPool pool = null;

其对应的iinvoke方法是这样的:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    
        if (compare(CLOSE_VAL,method)) { // 重点看这里,判断如果是close方法,则会调用returnConnection方法
            if (connection==null) return null; //noop for already closed.
            PooledConnection poolc = this.connection;
            this.connection = null;
            pool.returnConnection(poolc);
            return null;
        } else if (compare(TOSTRING_VAL,method)) {
            return this.toString();
        } else if (compare(GETCONNECTION_VAL,method) && connection!=null) {
            return connection.getConnection();
        } 
 
    }

我们注意,此处使用代理模式,会将各个方法的调用进行拦截,之后判断,如果是普通方法会转给对应的method对象执行,而如果待执行方法是close方法,则该方法不会被执行,而是由Interceptor执行pool的方法,将连接重新放回到池中,以备再次使用。

综上,我们看到,在池中,使用了两个队列,busy 和idle,分别代表忙闲两类连接,在释放连接的时候,即close方法执行的时候,会先把连接从busy队列中移除,同时将该连接再加入到idel队列中。从而实现连接的重复使用。

相关阅读

数据源连接池的原理及Tomcat中的应用

线程池的原理

怎样调试Tomcat源码

猜你喜欢

  1. 深度揭秘乱码问题背后的原因及解决方式

  2. WEB应用是怎么被部署的?

  3. 怎样调试Tomcat源码

  4. IDE里的Tomcat是这样工作的!

  5. 重定向与转发的本质区别

  6. 怎样阅读源代码

Tomcat那些事儿

本公众号由曾从事应用服务器核心研发的工程师维护。文章深入Tomcat源码,分析应用服务器的实现细节,工作原理及与之相关的技术,使用技巧,工作实战等。起于Tomcat但不止于此。同时会分享并发、JVM等,内容多为原创,欢迎关注。


扫描或长按下方二维码,即可关注!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值