Druid缓存

Posted 初学者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的工作情况,然后根据实际情况调整。

12. 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。

13. 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>

 

14. 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"      />

15. 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,只能够通过这种方法来更换。

以上是关于Druid缓存的主要内容,如果未能解决你的问题,请参考以下文章

java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven b

java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5

java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5

java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5

java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5

注解及AOP实现Redis缓存组件