(1)使用Presto中的Druid报错:recyle error 和 abandon connection 来深入研究Druid的底层实现原理

(1)本文主要是使用Druid作为Presto的连接池,所遇到的问题,以及的错误和解决方法

先看一下我的Druid的配置

 - druidDataSource.setTestWhileIdle(true);

 - druidDataSource.setTestOnBorrow(true);

 - druidDataSource.setTestOnReturn(false);

 - druidDataSource.setQueryTimeout(15);

 - druidDataSource.setMaxActive(20); 配置Druid的连接数的峰值

 - druidDataSource.setMaxWait(6000); 从连接池获取连接的超时等待时间,单位毫秒

 - druidDataSource.setMinIdle(1); 设置池子中的固定的空闲连接数

 - druidDataSource.setKeepAlive(true);

 - druidDataSource.setTimeBetweenEvictionRunsMillis(30000);Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明

 - druidDataSource.setMinEvictableIdleTimeMillis(60000);//配置一个连接在池中最小生存的时间,单位是毫秒;即最多允许一个连接空闲多长时间,超出此时间的连接,会被destroy线程关闭
  druidDataSource.setMaxEvictableIdleTimeMillis(90000);配置一个连接在池子中的最大存时间
  druidDataSource.setRemoveAbandoned(true);是否强制关闭连接时长大于removeAbandonedTimeoutMillis的连接
  druidDataSource.setRemoveAbandonedTimeout(180); 这个是强制关闭的时间值限定单位是秒
  //这里我开启了Druid的超时回收连接机制
  druidDataSource.setRemoveAbandoned(true);
  druidDataSource.setRemoveAbandonedTimeout(5);单位秒
  druidDataSource.setLogAbandoned(true); 这个如果打开的话,就可以看到Druid在回收的连接的时候报错信息:

首先会出现recyle error 的原因是因为我咋Druid的配置中开启了Druid的超时回收连接机制,Druid的内部回收线程会去回收Connection对象,所以会出现recyle error的报错,但是其内部的根本原因是因为Druid再回收的过程中遇到了错误,并且把日志打印出来了(这个是Druid默认打开的),这个Druid打开的目的就是为了告诉你,你的应用中有一些Connection回收的过程中失败,但是并不影响Druid真正把这个Connection给强制回收,但是此时你的程序肯定是有问题的。
解决方法:

  • 要么是直接加上这个配置,把Druid打印回收Connection的错误日志开关给关闭,但是这样指标不治本,druidDataSource.setLogAbandoned(false);
  • 自己去仔细的排查程序中可能会出现的Connection回收失败的问题

(2)去排查自己程序中是否存在Connection回收失败的问题

    /**
     * 获取Druid连接对象
     *
     * @return
     */
    public static DruidPooledConnection druidPooledConnection = null;
    public static synchronized PrestoConnection getConnection() {
        PrestoConnection conn = null;
        try {
            druidPooledConnection = druidDataSource.getConnection();
            conn = (PrestoConnection) druidPooledConnection.getConnection();
        } catch (SQLException e) {
            log.error("创建Connection失败异常信息 msg={}", " 原因", e);
        }
        return conn;
    }

看一下我业务中关闭connection的时候,其实关闭的是PrestoConnection,但是其实应该是关闭DruidPooledConnection。业务中的错误代码是这样的:

            try {
                if (!resultSet.isClosed() && !statement.isClosed()){
                    resultSet.close();
                    statement.close();
                    log.info(" Close the ResultSet success: {}, Close the statement success: {}", resultSet.isClosed(), statement.isClosed());
                }
                if (!prestoConnection.isClosed()) {
                    prestoConnection.close();
                    log.info(" Close the prestoConnection success: {}", prestoConnection.isClosed());
                }
            } catch (SQLException e) {
                log.error("关闭prestoConnection或者Statement失败异常信息 msg={}", "原因", e);
                e.printStackTrace();
            }

	把上面的关闭prestoConnection代码如果改成关闭druidPooledConnection就好了
			druidPooledConnection.close();

经过一系列的问题排查,最终定位,发现其真正的问题是因为我的DruidPooledConnection没有关闭(这个DruidPooledConnection是直接从DruidDataSource中获取到的),但是我在程序中每次查询完业务逻辑之后直接把我的PrestoConnection对象的连接给关闭了,关闭的不是我应该关闭的connection对象,所以Druid回收线程一直报错,说明Connection泄漏了

(3)总结底层原理的原因

首先因为不仅实现了Connection接口 而且还实现了PooledConnection 接口,但是PrestoConnection只实现了Connection接口,而我在使用的时候直接把PooledConnection强制转化为PrestoConnection了,那么其实PooledConnection这个相当于是我的逻辑连接,而每次我直接在调用PrestoConnection.close()的时候,其实关闭的物理连接(因为PrestoConnection并没有实现PooledConnection这个接口,所以根据不具有Druid中的逻辑连接特性)

  • PrestoConnection conn = (PrestoConnection) druidPooledConnection.getConnection();
  • DruidPooledConnection extends PoolableWrapper implements javax.sql.PooledConnection, Connection
  • public class PrestoConnection implements Connection
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章