Druid連接池(三)


十二、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

 


免責聲明!

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



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