Durid abandon connection错误分析

简介: Durid abandon connection错误分析

最近线上碰到一个问题,提示放弃数据库连接

2019-04-23 15:15:15.137 ERROR [Druid-ConnectionPool-Destroy-29530630]com.alibaba.druid.pool.DruidDataSource.removeAbandoned():2437 -abandon connection, owner thread: Thread-2187, connected at : 1556001108595, open stackTrace
    at java.lang.Thread.getStackTrace(Thread.java:1559)
    at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1252)
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4619)
    at com.alibaba.druid.filter.logging.LogFilter.dataSource_getConnection(LogFilter.java:874)
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4615)
    at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:666)
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4615)
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1176)
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1168)
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:104)
......
2019-04-23 15:15:15.137 ERROR [Druid-ConnectionPool-Destroy-29530630]com.alibaba.druid.pool.DruidDataSource.removeAbandoned():2437 -abandon connection, owner thread: Thread-2187, connected at : 1556001108595, open stackTrace
    at java.lang.Thread.getStackTrace(Thread.java:1559)
    at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1252)
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4619)
    at com.alibaba.druid.filter.logging.LogFilter.dataSource_getConnection(LogFilter.java:874)
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4615)
    at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:666)
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4615)
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1176)
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1168)
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:104)


spring事务的执行有以下几个步骤



1:获取连接 Connection con = DriverManager.getConnection()


2:取消事务的自动提交con.setAutoCommit(false);


3:设置事务的传播属性


4:设置事务的超时时间


5:执行CURD


6:提交事务/回滚事务 con.commit() / con.rollback();


7:关闭连接 conn.close();

@Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = null;
        try {
            if (txObject.getConnectionHolder() == null ||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                Connection newCon = this.dataSource.getConnection();
                if (logger.isDebugEnabled()) {
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            //获取连接
            con = txObject.getConnectionHolder().getConnection();
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
            // so we don't want to do it unnecessarily (for example if we've explicitly
            // configured the connection pool to set it already).
            //关闭自动提交
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }
            //设置事务传播属性
            prepareTransactionalConnection(con, definition);
            txObject.getConnectionHolder().setTransactionActive(true);
            int timeout = determineTimeout(definition);
            //设置事务超时时间
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }
            // Bind the connection holder to the thread.
            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
            }
        }
        catch (Throwable ex) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, this.dataSource);
                txObject.setConnectionHolder(null, false);
            }
            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
        }
    }


从上面代码我们可以得到这么几个重要的信息


1:数据的连接是在事务开始时就获取,而不是在执行curd时才获取。所以即时你的curd很快,但如果整体事务时间较长,仍然会超出数据源的最大时间限制(这个时间并不是事务的超时时间,而是数据源自身的一种保护机制,后面可以看到)


2:事务的隔离级别和超时时间是解析的@Transactional注解。


我们再来看下druid数据源的配置信息

<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"></property>
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true"/>
<!--&lt;!&ndash; 1800秒,也就是30分钟 &ndash;&gt;-->
<property name="removeAbandonedTimeout" value="1800"/>


这两个配置,可以理解为一种数据源自身的保护,如果Druid的销毁线程检测到数据库连接的占用时间已经超过了removeAbandonedTimeout设置的阈值,那么会强制断开此次数据库连接。


实际业务中,可能数据库连接占用时间超过removeAbandonedTimeout才被强制关闭。假如我们定义removeAbandonedTimeout为T1,timeBetweenEvictionRunsMillis为T2,那么事务占用数据库连接的最长时间在[T1, T1+T2]之间,也就是实际占用连接时间可能超过连接占用最大时间的阈值,这个要看轮询频率的大小而定。


//连接销毁任务
public class DestroyTask implements Runnable {
    @Override
    public void run() {
        shrink(true, keepAlive);
        if (isRemoveAbandoned()) {
            removeAbandoned();
        }
    }
}
public int removeAbandoned() {
    int removeCount = 0;
    long currrentNanos = System.nanoTime();
    List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
    activeConnectionLock.lock();
    try {
        Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
        for (; iter.hasNext();) {
            DruidPooledConnection pooledConnection = iter.next();
            if (pooledConnection.isRunning()) {
                continue;
            }
            long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
            //计算时间,超出阈值的添加到销毁列表中
            if (timeMillis >= removeAbandonedTimeoutMillis) {
                iter.remove();
                pooledConnection.setTraceEnable(false);
                abandonedList.add(pooledConnection);
            }
        }
    } finally {
        activeConnectionLock.unlock();
    }
    ...
}


以下伪代码中,即时操作2非常快,但是操作1和操作3较慢,有可能导致数据库连接未释放,而数据源本身的保护机制可能会强制放弃该连接,导致数据库操作失败。所以最好的方式是尽量将数据库操作单独添加事务,而不是将整个与数据库操作毫无关系的操作放到一起,这样会占用不必要的数据库资源(注意:不能简单的将操作2写到一个内部方法中,因为spring事务的代理都是针对外层方法调用,所以最好是将操作2提炼到一个service中,再添加事务支持)

@Transactional(...)
public a(){
    //非数据库操作1(IO操作等,较慢)
    //数据库操作2(非常快)
    //非数据库操作3(较慢)
}


相关文章
|
Java API Maven
敏感数据的保护伞——SpringBoot Jasypt加密库的使用
我们经常会在yml配置文件中存放一些敏感数据,比如数据库的用户名、密码,第三方应用的秘钥等等。这些信息直接以明文形式展示在文件中,无疑是存在较大的安全隐患的,所以今天这篇文章,我会借助jasypt实现yml文件中敏感信息的加密处理。
4269 1
敏感数据的保护伞——SpringBoot Jasypt加密库的使用
Kam
|
druid
druid数据连接池异常 connection holder is null 解决方案
druid数据连接池异常 connection holder is null 解决方案
Kam
2542 0
|
消息中间件 SQL 存储
超详细的RabbitMQ入门,看这篇就够了!
RabbitMQ入门,看这篇就够了
216241 68
|
12月前
|
Java Spring
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
本文介绍了Spring Boot中静态资源的访问位置、如何进行静态资源访问测试、自定义静态资源路径和静态资源请求映射,以及如何处理自定义静态资源映射对index页面访问的影响。提供了两种解决方案:取消自定义静态资源映射或编写Controller来截获index.html的请求并重定向。
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
|
11月前
idea 打不开,电脑上下了多个IDEA,新下的IDEA双击打不开,新版IDEA打不开,超实用简单解决办法
一个简单实用的方法来解决新安装的 IntelliJ IDEA 打不开的问题,通常是由于旧版本未卸载干净导致配置文件冲突,建议删除旧版的配置文件来解决这个问题。
2798 1
|
负载均衡 固态存储 Linux
阿里云轻量应用服务器、云服务器、gpu云服务器最新收费标准参考
轻量应用服务器、云服务器、gpu云服务器是阿里云服务器产品中,比较热门的云服务器产品类型,不同类型的云服务器产品收费模式与收费标准是不一样的,本文为大家展示这几个云服务器产品的最新收费标准情况,以供参考。
阿里云轻量应用服务器、云服务器、gpu云服务器最新收费标准参考
|
SQL druid Java
解决 ‘The last packet successfully received from the server was xxx milliseconds ago‘ 问题
解决 ‘The last packet successfully received from the server was xxx milliseconds ago‘ 问题
6193 0
|
网络协议 Java 关系型数据库
一篇文章彻底理解数据库的各种 JDBC 超时参数 2
一篇文章彻底理解数据库的各种 JDBC 超时参数
|
SQL Java 数据库连接
【Java】已解决java.sql.SQLException异常
【Java】已解决java.sql.SQLException异常
2403 0
|
IDE Java Maven
【Java】已解决:java.lang.NoSuchMethodError异常
【Java】已解决:java.lang.NoSuchMethodError异常
5332 0