阿里DRUID 配置說明及分析


        今天我們說說數據源和數據庫連接池,熟悉java開發的同仁應該都了解C3PO,在這里不做過多的贅述了,今天我們說的是阿里DRUID,druid是后起之秀,因為它的優秀很快占領了使用市場,下邊我們一起來看看druid數據源的配置以及druid監控的配置和監控的實現邏輯。

1、druid數據源配置

       下面是druid的數據源配置項,這些配置項都是com.alibaba.druid.pool.DruidDataSource類和其基類com.alibaba.druid.pool.DruidAbstractDataSource的public final屬性,這些配置型和C3P0的數據源配置項基本一樣,有個別的是明白發生了變化但是參數所表示的意思不變,還有一些參數是druid自己擴展的,其中filters屬性就是傑出代表,次屬性是DruidAbstractDataSource類的,是一個List<Filter>的集合,此屬性提供了三個可選值:監控統計用的stat、日志用的log4j、 防御sql注入的wall,這三個值可以單獨使用也可以兩兩組合或者一起使用,組合使用的時候不同值之間用逗號隔開。有人可能會有疑問了,不是一個List集合嗎,為什么這里卻是用逗號分隔的,那是因為druid在賦值的時候有特殊處理,至於是如何處理的在下邊我們會說到。

配置 缺省值 說明
name   配置這個屬性的意義在於,如果存在多個數據源,監控的時候
可以通過名字來區分開來。如果沒有配置,將會生成一個名字,
格式是:"DataSource-" + System.identityHashCode(this)
jdbcUrl   連接數據庫的url,不同數據庫不一樣。例如:
mysql : jdbc:mysql://10.20.153.104:3306/druid2 
oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username   連接數據庫的用戶名
password   連接數據庫的密碼。如果你不希望密碼直接寫在配置文件中,
可以使用ConfigFilter。詳細看這里:
https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName 根據url自動識別 這一項可配可不配,如果不配置druid會根據url自動識別dbType,
然后選擇相應的driverClassName
initialSize 0 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,
或者第一次getConnection時
maxActive 8 最大連接池數量
maxIdle 8 已經不再使用,配置了也沒效果
minIdle   最小連接池數量
maxWait   獲取連接時最大等待時間,單位毫秒。配置了maxWait之后,
缺省啟用公平鎖,並發效率會有所下降,
如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。
poolPreparedStatements false 是否緩存preparedStatement,也就是PSCache。
PSCache對支持游標的數據庫性能提升巨大,比如說oracle。
在mysql5.5以下的版本中沒有PSCache功能,建議關閉掉。
5.5及以上版本有PSCache,建議開啟。
maxOpenPreparedStatements -1 要啟用PSCache,必須配置大於0,當大於0時,
poolPreparedStatements自動觸發修改為true。
在Druid中,不會存在Oracle下PSCache占用內存過多的問題,
可以把這個數值配置大一些,比如說100
validationQuery   用來檢測連接是否有效的sql,要求是一個查詢語句。
如果validationQuery為null,testOnBorrow、testOnReturn、
testWhileIdle都不會其作用。
testOnBorrow true 申請連接時執行validationQuery檢測連接是否有效,
做了這個配置會降低性能。
testOnReturn false 歸還連接時執行validationQuery檢測連接是否有效,
做了這個配置會降低性能
testWhileIdle false 建議配置為true,不影響性能,並且保證安全性。
申請連接的時候檢測,如果空閑時間大於
timeBetweenEvictionRunsMillis,
執行validationQuery檢測連接是否有效。
timeBetweenEvictionRunsMillis   有兩個含義:
1) Destroy線程會檢測連接的間隔時間
 2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
numTestsPerEvictionRun   不再使用,一個DruidDataSource只支持一個EvictionRun
minEvictableIdleTimeMillis   Destory線程中如果檢測到當前連接的最后活躍時間和當前時間的差值大於
minEvictableIdleTimeMillis,則關閉當前連接。
connectionInitSqls   物理連接初始化的時候執行的sql
exceptionSorter 根據dbType自動識別 當數據庫拋出一些不可恢復的異常時,拋棄連接
filters   屬性類型是字符串,通過別名的方式配置擴展插件,
常用的插件有:
監控統計用的filter:stat 
日志用的filter:log4j
 防御sql注入的filter:wall
proxyFilters   類型是List<com.alibaba.druid.filter.Filter>,
如果同時配置了filters和proxyFilters,
是組合關系,並非替換關系
removeAbandoned   對於建立時間超過removeAbandonedTimeout的連接強制關閉
removeAbandonedTimeout   指定連接建立多長時間就需要被強制關閉
logAbandoned   指定發生removeabandoned的時候,是否記錄當前線程的堆棧信息到日志中

2、druid監控的配置與監控訪問

 要使用druid監控需要做好兩個配置:

1)、在配置數據源時需要配置filters並且賦值你需要使用的監控項(stat、log4j、wall);

2)、需要在項目的web.xml中配置druid的自定義servlet(com.alibaba.druid.support.http.StatViewServlet),配置樣例如下代碼所示:

<servlet>  
        <servlet-name>DruidStatView</servlet-name>  
        <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>  
</servlet>  
<servlet-mapping>  
        <servlet-name>DruidStatView</servlet-name>  
        <url-pattern>/druid/*</url-pattern>  
 </servlet-mapping>

3)、監控系統的訪問:http://IP:PORT/projectName/druid/(http://localhost:8088/cd_management/druid/sql.html),監控效果如下圖所示:

3、filters屬性的賦值邏輯

        上面有提到filters的配置項有三個,可以隨機組合也可以一起使用,也有提到filters是一個List集合,那我們來看看druid是如何賦值的,如下源碼所示,賦值時調用的setFilters(String filters)方法,最終是通過逗號分隔為數組然后遍歷調用FilterManager.loadFilter(this.filters, item.trim()),然后用反射機制生成相應的對象並添加到Filter集合。

public void setFilters(String filters) throws SQLException {
        if (filters != null && filters.startsWith("!")) {
            filters = filters.substring(1);
            this.clearFilters();
        }
        this.addFilters(filters);
    }

    public void addFilters(String filters) throws SQLException {
        if (filters == null || filters.length() == 0) {
            return;
        }

        String[] filterArray = filters.split("\\,");

        for (String item : filterArray) {
            FilterManager.loadFilter(this.filters, item.trim());
        }
    }
 public static void loadFilter(List<Filter> filters, String filterName) throws SQLException {
        if (filterName.length() == 0) {
            return;
        }

        String filterClassNames = getFilter(filterName);

        if (filterClassNames != null) {
            for (String filterClassName : filterClassNames.split(",")) {
                if (existsFilter(filters, filterClassName)) {
                    continue;
                }

                Class<?> filterClass = Utils.loadClass(filterClassName);

                if (filterClass == null) {
                    LOG.error("load filter error, filter not found : " + filterClassName);
                    continue;
                }

                Filter filter;

                try {
                    filter = (Filter) filterClass.newInstance();
                } catch (ClassCastException e) {
                    LOG.error("load filter error.", e);
                    continue;
                } catch (InstantiationException e) {
                    throw new SQLException("load managed jdbc driver event listener error. " + filterName, e);
                } catch (IllegalAccessException e) {
                    throw new SQLException("load managed jdbc driver event listener error. " + filterName, e);
                }

                filters.add(filter);
            }

            return;
        }

        if (existsFilter(filters, filterName)) {
            return;
        }

        Class<?> filterClass = Utils.loadClass(filterName);
        if (filterClass == null) {
            LOG.error("load filter error, filter not found : " + filterName);
            return;
        }

        try {
            Filter filter = (Filter) filterClass.newInstance();
            filters.add(filter);
        } catch (Exception e) {
            throw new SQLException("load managed jdbc driver event listener error. " + filterName, e);
        }
    }

4、druid監控實現邏輯

     要說明druid監控邏輯從如下三個方面切入分析:

      1)、監控的數據什么時候生成

       在這里我們拿Spring和druid整合案例來分析說明druid監控數據的生成,上面我們有提到要使用druid的監控空能需要配置filters,並且filters可以配置多個,這里druid關於這些filter的處理其實借鑒了過濾器鏈的原理,druid關於監控數據的收集處理邏輯是這樣的,我們從Spring的JdbcTemplate類開始看,如下源碼一,是一個查詢的處理,rs = ps.executeQuery()是PreparedStatement開始執行sql從數據庫查詢數據的開始,這里我們給ps對象賦予的是DruidPooledPreparedStatement類對象,所以進入DruidPooledPreparedStatement類我們來看它的具體實現。

        源碼二是DruidPooledPreparedStatement類對executeQuery方法的實現,這個方法里面最關鍵的是ResultSet rs = stmt.executeQuery()這句,stmt是PreparedStatementProxyImpl類的類對象。

        源碼三是PreparedStatementProxyImpl類對executeQuery方法的實現,這個方法實現中調用了父類的createChain()方法,源碼四為父類方法實現,這個方法的返回值是一個過濾器鏈類FilterChainImpl類對象,FilterChainImpl類的

preparedStatement_executeQuery(PreparedStatementProxy statement)方法實現如源碼五。

public <T> T query(
            PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
            throws DataAccessException {

        Assert.notNull(rse, "ResultSetExtractor must not be null");
        logger.debug("Executing prepared SQL query");

        return execute(psc, new PreparedStatementCallback<T>() {
            @Override
            public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
                ResultSet rs = null;
                try {
                    if (pss != null) {
                        pss.setValues(ps);
                    }
                    rs = ps.executeQuery();
                    ResultSet rsToUse = rs;
                    if (nativeJdbcExtractor != null) {
                        rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
                    }
                    return rse.extractData(rsToUse);
                }
                finally {
                    JdbcUtils.closeResultSet(rs);
                    if (pss instanceof ParameterDisposer) {
                        ((ParameterDisposer) pss).cleanupParameters();
                    }
                }
            }
        });
    }
public ResultSet executeQuery() throws SQLException {
        checkOpen();

        incrementExecuteCount();
        transactionRecord(sql);

        oracleSetRowPrefetch();

        conn.beforeExecute();
        try {
            ResultSet rs = stmt.executeQuery();

            if (rs == null) {
                return null;
            }

            DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
            addResultSetTrace(poolableResultSet);

            return poolableResultSet;
        } catch (Throwable t) {
            errorCheck(t);

            throw checkException(t);
        } finally {
            conn.afterExecute();
        }
    }
public ResultSet executeQuery() throws SQLException {
        firstResultSet = true;

        updateCount = null;
        lastExecuteSql = sql;
        lastExecuteType = StatementExecuteType.ExecuteQuery;
        lastExecuteStartNano = -1L;
        lastExecuteTimeNano = -1L;

        return createChain().preparedStatement_executeQuery(this);
    }
 public FilterChainImpl createChain() {
        FilterChainImpl chain = this.filterChain;
        if (chain == null) {
            chain = new FilterChainImpl(this.getConnectionProxy().getDirectDataSource());
        } else {
            this.filterChain = null;
        }

        return chain;
    }
@Override
    public ResultSetProxy preparedStatement_executeQuery(PreparedStatementProxy statement) throws SQLException {
        if (this.pos < filterSize) {
            return nextFilter().preparedStatement_executeQuery(this, statement);
        }

        ResultSet resultSet = statement.getRawObject().executeQuery();
        if (resultSet == null) {
            return null;
        }
        return new ResultSetProxyImpl(statement, resultSet, dataSource.createResultSetId(),
                statement.getLastExecuteSql());
    }

        看了如上源碼我們會發現FilterChainImpl類的preparedStatement_executeQuery方法執行的時候會先執行過濾器類的此方法,所以我們看看過濾器類做了什么,這里我們拿SQL監控的過濾器類(FilterEventAdapter)來分析,如下源碼是此類的方法實現。我看可以看到此方法先調用了statementExecuteQueryBefore(statement, statement.getSql())方法,然后調用了下一個過濾器類的查詢方法,在方法正常執行以后又調用了statementExecuteQueryAfter(statement, statement.getSql(), resultSet)方法,在方法執行異常的時候調用了statement_executeErrorAfter(statement, statement.getSql(), error),這些方法的作用就是保存SQL執行中的監控數據。說到這里從流程上就說明了druid監控數據的來源。

public ResultSetProxy preparedStatement_executeQuery(FilterChain chain, PreparedStatementProxy statement)
                                                                                                             throws SQLException {
        try {
            statementExecuteQueryBefore(statement, statement.getSql());

            ResultSetProxy resultSet = chain.preparedStatement_executeQuery(statement);

            if (resultSet != null) {
                statementExecuteQueryAfter(statement, statement.getSql(), resultSet);

                resultSetOpenAfter(resultSet);
            }

            return resultSet;
        } catch (SQLException error) {
            statement_executeErrorAfter(statement, statement.getSql(), error);
            throw error;
        } catch (RuntimeException error) {
            statement_executeErrorAfter(statement, statement.getSql(), error);
            throw error;
        } catch (Error error) {
            statement_executeErrorAfter(statement, statement.getSql(), error);
            throw error;
        }
    }

      2)、監控的數據保存到哪里

     這里我們簡單說明,數據是保存到DruidDataSource類的dataSourceStat對象中。

      3)、監控的請求如何處理的

              要使用druid的監控功能需要配置com.alibaba.druid.support.http.StatViewServlet,這是一個繼承自HttpServlet的servlet,用來處理訪問druid監控的請求,具體處理流程如下:

         

5、如何去除監控頁面的廣告

       1) 使用過druid的同仁應該都了解,druid的監控頁面加載以后,footer頁是有阿里的廣告的如下圖所示,如果是一個商業項目這個是很不雅也是不允許的,那么我們來看看如何去除廣告。

 

       

       2)要去除這個廣告需要修改druid.jar的源碼文件,具體方法是,用winRAR打開jar包,在druid-1.1.6.jar\support\http\resources\js\common.js路徑下找到文件,修改common.js中如下圖所示的代碼,刪除buildFooter函數中的代碼即可:

buildFooter : function() {

            var html ='<footer class="footer">'+
                      '            <div class="container">'+
                      '<a href="https://render.alipay.com/p/s/taobaonpm_click/druid_banner_click" target="new"><img src="https://render.alipay.com/p/s/taobaonpm_click/druid_banner"></a><br/>' +
                        '    powered by <a href="https://github.com/alibaba/" target="_blank">AlibabaTech</a> & <a href="http://www.sandzhang.com/" target="_blank">sandzhang</a> & <a href="http://melin.iteye.com/" target="_blank">melin</a> & <a href="https://github.com/shrekwang" target="_blank">shrek.wang</a>'+
                        '            </div>'+
                      ' </footer>';
            $(document.body).append(html);
        },

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM