(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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章