搞懂TomcatJdbc之连接归还

前言

正所谓有借有还,当业务线程从TomcatJdbc数据库连接池获取到连接并使用完毕后,随即就应该将借出的连接归还回去。本文将结合源码,分析TomcatJdbc数据库连接池的连接的归还实现。Tomcat版本为9.0.82

正文

TomcatJdbc数据库连接池里的连接的类型是PooledConnection,但最终业务线程借出来的连接实际是一个代理连接对象,这是因为每一个连接从TomcatJdbc数据库连接池中被获取出来时,都会调用ConnectionPool#setupConnection方法来基于动态代理来应用拦截器的功能,我们配置的所有拦截器都会被构造成一个拦截器的责任链,这个责任链中除了尾节点的其余节点类型都是JdbcInterceptor,尾节点的类型是ProxyConnection,下面是JdbcInterceptorProxyConnectionPooledConnection之间的关系类图。

TomcatJdbc连接类图

那么就很清晰了,拦截器的责任链实际就是InvocationHandler的责任链,那么调用代理连接对象的方法时,就会调用到拦截器链,从而拦截器的逻辑会执行,同时拦截器链的尾节点类型是ProxyConnection,而ProxyConnection持有PooledConnection,所以最终PooledConnection会被调用到,下面就看一下ProxyConnectioninvoke() 方法的实现。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (compare(ISCLOSED_VAL, method)) {
        return Boolean.valueOf(isClosed());
    }
    if (compare(CLOSE_VAL, method)) {
        // 代理close()方法
        if (connection == null) {
            return null;
        }
        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) {
        // 代理getConnection()方法
        return connection.getConnection();
    } else if (method.getDeclaringClass().isAssignableFrom(XAConnection.class) && connection != null) {
        try {
            return method.invoke(connection.getXAConnection(), args);
        } catch (Throwable t) {
            if (t instanceof InvocationTargetException) {
                throw t.getCause() != null ? t.getCause() : t;
            } else {
                throw t;
            }
        }
    }
    if (isClosed()) {
        throw new SQLException("Connection has already been closed.");
    }
    if (compare(UNWRAP_VAL, method)) {
        return unwrap((Class<?>)args[0]);
    } else if (compare(ISWRAPPERFOR_VAL, method)) {
        return Boolean.valueOf(this.isWrapperFor((Class<?>)args[0]));
    }
    try {
        PooledConnection poolc = connection;
        if (poolc != null) {
            // 调用到底层物理连接
            return method.invoke(poolc.getConnection(), args);
        } else {
            throw new SQLException("Connection has already been closed.");
        }
    } catch (Throwable t) {
        if (t instanceof InvocationTargetException) {
            throw t.getCause() != null ? t.getCause() : t;
        } else {
            throw t;
        }
    }
}

通过上述代码可以知道,外层从TomcatJdbc数据库连接池拿到一个连接后,所有对连接的操作,均会由ProxyConnection完成代理,在ProxyConnection中会判断如何完成当前操作,例如本文讨论的连接归还,则是通过ConnectionPool#returnConnection方法完成,具体源码实现如下所示。

protected void returnConnection(PooledConnection con) {
    if (isClosed()) {
        // 如果连接池已经关闭则直接释放当前待归还的连接
        release(con);
        return;
    }

    if (con != null) {
        try {
            returnedCount.incrementAndGet();
            con.lock();
            // 如果当前归还的连接已经被标记为已放弃的可疑连接则打印一些记录日志
            if (con.isSuspect()) {
                if (poolProperties.isLogAbandoned() && log.isInfoEnabled()) {
                    log.info("Connection(" + con + ") that has been marked suspect was returned."
                            + " The processing time is " + (System.currentTimeMillis() - con.getTimestamp()) + " ms.");
                }
                if (jmxPool != null) {
                    jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.SUSPECT_RETURNED_NOTIFICATION,
                            "Connection(" + con + ") that has been marked suspect was returned.");
                }
            }
            // 将待归还连接从busy队列移除
            if (busy.remove(con)) {
                // 判断连接是否应该被直接释放
                if (!shouldClose(con, PooledConnection.VALIDATE_RETURN) && reconnectIfExpired(con)) {
                    con.clearWarnings();
                    con.setStackTrace(null);
                    // 更新连接的时间戳并消除嫌疑
                    con.setTimestamp(System.currentTimeMillis());
                    // 1. 如果idle队列已经达到上限则释放连接
                    // 2. idle队列未达到上限则尝试将连接归还到idle队列
                    // 3. 如果归还失败则释放连接
                    if (((idle.size() >= poolProperties.getMaxIdle()) && !poolProperties.isPoolSweeperEnabled()) || (!idle.offer(con))) {
                        if (log.isDebugEnabled()) {
                            log.debug("Connection [" + con + "] will be closed and not returned to the pool, idle[" + idle.size() + "]>=maxIdle["
                                    + poolProperties.getMaxIdle() + "] idle.offer failed.");
                        }
                        release(con);
                    }
                } else {
                    // 进入这里表示shouldClose()返回true
                    // 即连接应该被直接释放而不是归还到连接池中
                    if (log.isDebugEnabled()) {
                        log.debug("Connection [" + con + "] will be closed and not returned to the pool.");
                    }
                    release(con);
                }
            } else {
                // 从busy队列移除失败则直接释放连接
                if (log.isDebugEnabled()) {
                    log.debug("Connection [" + con + "] will be closed and not returned to the pool, busy.remove failed.");
                }
                release(con);
            }
        } finally {
            con.unlock();
        }
    }
}

上述连接归还逻辑小结如下。

  1. 如果连接池已经关闭则直接释放连接。如果连接池已经关闭,则连接也没必要再归还到连接池,而释放连接,则是直接关闭底层的物理连接;
  2. 打印被标记为已放弃的可疑连接信息。可疑连接,即suspect设置为true的连接,只有在连接借出去且达到suspectTimeout还没归还的连接会被设置suspecttrue
  3. 将连接从busy队列移除。如果移除失败,则直接释放连接;
  4. 判断连接是否应该直接被释放而不是归还。这里的判断是通过调用shouldClose() 方法来判断的,如果该方法返回false,则直接释放连接;
  5. 判断idle队列是否已经满了。如果满了,则直接释放连接;
  6. idle队列未满时将连接归还到idle队列。如果归还失败,则直接释放连接。

流程图如下所示。

数据库连接池-TomcatJdbc归还连接流程图

最后再看一下上面提到的ConnectionPool#shouldClose方法,该方法用于判断待归还的连接是否需要直接被释放,源码实现如下所示。

protected boolean shouldClose(PooledConnection con, int action) {
    if (con.getConnectionVersion() < getPoolVersion()) {
        return true;
    }
    if (con.isDiscarded()) {
        // 已经被连接池丢弃的连接需要直接释放
        return true;
    }
    if (isClosed()) {
        // 连接池已经被关闭则连接也需要直接释放
        return true;
    }
    if (!con.validate(action)) {
        // 配置了归还连接校验且校验不通过则连接需要直接释放
        return true;
    }
    if (!terminateTransaction(con)) {
        // 终止事务失败时连接需要直接释放
        return true;
    }
    // 上述判断均通过时则连接不需要直接释放
    return false;
}

其实ConnectionPool#shouldClose方法中最重要的就是在配置testOnReturntrue时会在连接归还到连接池前做一次连接校验,以确保归还的连接都是可用的连接。

总结

所谓借出连接,其实就是从TomcatJdbcidle队列中获取一个连接,这个借出的连接在被业务线程使用的同时,也会同时被放在busy队列中。

那么归还连接,其实就是把连接从busy队列移除然后再放回idle队列,我们也可以通过配置testOnReturntrue来让连接被归还到idle队列前进行一次校验,校验失败的连接会直接被释放,以确保每次归还的连接都是一个可用的连接。


链接:https://juejin.cn/post/7316409548994134054

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值