十二、Druid緩存
連接Oracle數據庫,打開PSCache,在其他的數據庫連接池都會存在內存占用過多的問題,Druid是唯一解決這個問題的連接池。
Oracle數據庫下PreparedStatementCache內存問題解決方案:
Oracle支持游標,一個PreparedStatement對應服務器一個游標,如果PreparedStatement被緩存起來重復執行,PreparedStatement沒有被關閉,服務器端的游標就不會被關閉,性能提高非常顯著。在類似SELECT * FROM T WHERE ID = ?這樣的場景,性能可能是一個數量級的提升。
由於PreparedStatementCache性能提升明顯,DruidDataSource、DBCP、JBossDataSource、WeblogicDataSource都實現了PreparedStatementCache。
PreparedStatementCache帶來的問題
阿里巴巴在使用jboss連接池做PreparedStatementCache時,遇到了full gc頻繁的問題。通過mat來分析jmap dump的結果,發現T4CPreparedStatement占內存很多,出問題的幾個項目,有的300M,有的500M,最誇張的900M。這些應用都是用jboss連接池訪問Oracle數據庫,T4CPreparedStatement是Oracle JDBC Driver的PreparedStatement一種實現。 oracle driver不是開源,通過逆向工程以及mat分析,發現其中占內存的是字段char[] defineChars,defineChars大小的計算公式是這樣的:
defineChars大小 = rowSize * rowPrefetchCount
rowPrefetchCount在Oracle中,缺省值為10。
其中rowSize是執行查詢設計的每一列的大小的和。計算公式是:
rowSize = col_1_size + col_2_size + ... + col_n_size
很悲劇,有些列數據類型是varchar2(4000),於是rowSize巨大,很多個表關聯的SQL,rowSize可能高達數十K,再乘以rowPrefetchCount,defineChars大小接近1M。可以想想,maxPoolSize設置為30,PreparedStatementCacheSize設置為50的場景下,是可能導致PreparedStatementCache占據上G的內存。 實際測試得到的結果如下:
varchar2(4000) col_size 4000 chars clob -> col_size col_size 4000 bytes
實際占據內存的公式:
占據內存大小峰值 = defineChars大小 * PreparedStatementCacheSize * MaxPoolSize
我們實際分析,一個應用運行的SQL大約數百條,PreparedStatementCacheSize為50,PreparedStatementCache的算法為LRU,很多的SQL執行之后,在Cache中HitCount為0就被淘汰了,淘汰的過程,其位置從第1移到第50,這個漫長的過程導致了defineChars不能夠被young gc回收。
Druid的解決方案
使用OracleDriver提供的PreparedStatementCache支持方法,清理PreparedStatement所持有的buffer。 Oracle在10.x和11.x的Driver中,都提供了如下管理PreparedStatementCache的接口,如下:
package oracle.jdbc.internal; import java.sql.SQLException; public interface OraclePreparedStatement extends oracle.jdbc.OraclePreparedStatement, OracleStatement { public void enterImplicitCache() throws SQLException; public void exitImplicitCacheToActive() throws SQLException; public void exitImplicitCacheToClose() throws SQLException; }
DruidDataSource在管理Oracle PreparedStatement Cache時,調用了上述方法。當調用了enterImplicitCache之后,T4CPreparedStatement中的defineChars和defineBytes都會被清空。
測試表明,通過上述處理,能夠有效降低內存。
根據PreparedStatement執行的結果,計算RowPrefetch大小 DrudDataSource對在PreparedStatement.executeQuery和execute方法返回的ResultSet做監控統計執行SQL返回的行數,然后根據統計的結果來設置rowPrefetchSize。例如SQL
SELECT * FROM ORDER WHERE ID = ?
這樣的SQL每次返回的紀錄數量都是0或者1,根據這個統計的最大值來設置rowPrefetchSize。如果最大值為1,則需要設置rowPrefetchSize為2。
計算公式如下:
int maxRowFetchCount = max(resultSet.size) + 1; if (maxRowFetchCount > defaultRowPrefetch) { maxRowFetchCount = defaultRowPreftech; } prearedStatement.rowPrefetch = maxRowFetchCount;
根據生產環境的監控統計,大多數的SQL返回的行數都是比較小的,通常是1。通過這種算法,能夠減少PreparedStatementCache的內存占用。
添加PreparedStatementCache計數器 包括:
PreparedStatementCacheCurrentSize PreparedStatementCacheDeleteCount 緩存刪除次數 PreparedStatementCacheHitCount 緩存命中次數 PreparedStatementCacheMissCount 緩存不命中次數 PreparedStatementCacheAccessCount 緩存訪問次數
通過這五個計數器,我們清晰了解PreparedStatementCache的工作情況,然后根據實際情況調整。
十三、Druid對比
各種數據庫連接池對比
主要功能對比
|
Druid |
BoneCP |
DBCP |
C3P0 |
Proxool |
JBoss |
LRU |
是 |
否 |
是 |
否 |
是 |
是 |
PSCache |
是 |
是 |
是 |
是 |
否 |
否 |
PSCache-Oracle-Optimized |
是 |
否 |
否 |
否 |
否 |
否 |
ExceptionSorter |
是 |
否 |
否 |
否 |
否 |
是 |
LRU
LRU是一個性能關鍵指標,特別Oracle,每個Connection對應數據庫端的一個進程,如果數據庫連接池遵從LRU,有助於數據庫服務器優化,這是重要的指標。在測試中,Druid、DBCP、Proxool是遵守LRU的。BoneCP、C3P0則不是。BoneCP在mock環境下性能可能好,但在真實環境中則就不好了。
PSCache
PSCache是數據庫連接池的關鍵指標。在Oracle中,類似SELECT NAME FROM USER WHERE ID = ?這樣的SQL,啟用PSCache和不啟用PSCache的性能可能是相差一個數量級的。Proxool是不支持PSCache的數據庫連接池,如果你使用Oracle、SQL Server、DB2、Sybase這樣支持游標的數據庫,那你就完全不用考慮Proxool。
PSCache-Oracle-Optimized
Oracle 10系列的Driver,如果開啟PSCache,會占用大量的內存,必須做特別的處理,啟用內部的EnterImplicitCache等方法優化才能夠減少內存的占用。這個功能只有DruidDataSource有。如果你使用的是Oracle Jdbc,你應該毫不猶豫采用DruidDataSource。
ExceptionSorter
ExceptionSorter是一個很重要的容錯特性,如果一個連接產生了一個不可恢復的錯誤,必須立刻從連接池中去掉,否則會連續產生大量錯誤。這個特性,目前只有JBossDataSource和Druid實現。Druid的實現參考自JBossDataSource。
十四、Druid遷移
dbcp遷移:
DruidDataSource的配置是兼容DBCP的。從DBCP遷移到DruidDataSource,只需要修改數據源的實現類就可以了。
DBCP的數據庫連接池的實現是:
org.apache.commons.dbcp.BasicDataSource
替換為:
com.alibaba.druid.pool.DruidDataSource
如果需要使用Druid的其他配置,可以參考https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE
例子
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
</bean>
十五、Druid特性
ExceptionSorter是JBoss DataSource中的優秀特性,Druid也有一樣功能的ExceptionSorter,但不用手動配置,自動識別生效的。
maxIdle是Druid為了方便DBCP用戶遷移而增加的,maxIdle是一個混亂的概念。連接池只應該有maxPoolSize和minPoolSize,druid只保留了maxActive和minIdle,分別相當於maxPoolSize和minPoolSize。
DruidDataSource支持JNDI配置,具體實現的類是這個:
com.alibaba.druid.pool.DruidDataSourceFactory,你可以閱讀代碼加深理解。
com.alibaba.druid.pool.DruidDataSourceFactory實現了javax.naming.spi.ObjectFactory,可以作為JNDI數據源來配置。
Tomcat JNDI配置
在Tomcat使用JNDI配置DruidDataSource,在/conf/context.xml中,在中加入如下配置:
<Resource name="jdbc/druid-test" factory="com.alibaba.druid.pool.DruidDataSourceFactory" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" url="jdbc:derby:memory:tomcat-jndi;create=true" />
前半部分是基本信息,不能少的,后半部分是連接池的參數,具體參數看這里,大多數情況driverClassName可以自動識別的
添加Filter
<Resource name="jdbc/druid-test" factory="com.alibaba.druid.pool.DruidDataSourceFactory" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" url="jdbc:derby:memory:tomcat-jndi;create=true" filters="stat" />
十六、Druid更換
Druid提供了一個中完全平滑遷移DBCP的辦法。
1) 從http://repo1.maven.org/maven2/com/alibaba/druid/druid-wrapper/ 下載druid-wrapper-xxx.jar
2) 加入druid-xxx.jar
3) 從你的WEB-INF/lib/中刪除dbcp-xxx.jar
4) 按需要加上配置,比如JVM啟動參數加上-Ddruid.filters=stat,動態配置druid的filters
這種用法,使得可以在一些非自己開發的應用中使用Druid,例如在sonar中部署druid,sonar是一個使用jruby開發的web應用,寫死了DBCP,只能夠通過這種方法來更換。
文章轉自:http://blog.csdn.net/yinxiangbing/article/details/47905447