几月前, 我接到一个银行的J2EE应用系统JSF+HIBERNATE+SPRING+WebService性能优化合同,静下心来几个月好好研究了一下J2EE 系统的关键性能优化问题,结果非常令人吃惊,提交测试后其并发运行性能得到几倍到几十倍的提高,我想把其中的一些关键技术点总结出来,与大家共享,今天就 贡献最重要的部分,HIBERNATE性能优化,以后再分别讨论JSF的分页技术,SPRING的WebServiceTemplate的应用,这些也都 是IBM公司目前正在全球推广使用的最前沿的技术。 对Hibernate优化来讲,关键点包括the second-level cache 如ehcache(OScache,JBosscache), 数据fetch策略以避免太密集的数据库query accesses 和增进并发性. 开发环境是:IBM Websphere 6.1server/RAD7/IBM DB2 9/Spring 2.5/Hibernate 3.0/JSF. 1.首先建立ehcache, 我做了一下几件事: a)在ApplicationContext.xml文件中增加一些配置:
... < bean id= "hibernateSessionID" class= "org.springframework.orm.hibernate3.LocalSessionFactoryBean" > < property name= "hibernateProperties" > < props> …… < prop key= "hibernate.cache.provider_class" > org. hibernate. cache. EhCacheProvider< / prop> < prop key= "hibernate.cache.use_second_level_cache" > True< / prop> < prop key= "hibernate.cache.use_query_cache" > true< / prop> < / props> < / property> < / bean> < bean id= "cacheManager" class= "org.springframework.cache.ehcache.EhCacheManagerFactoryBean" > < property name= "configLocation" value= "/WEB-INF/ehcache.xml" / > < / bean> < !-- A facade to the Ehcache cache class --> < bean id= "cacheProviderFacade" class= "org.springmodules.cache.provider.ehcache.EhCacheFacade" > < property name= "cacheManager" ref= "cacheManager" / > < / bean> ... | b) 在ORM映射文件中增加属性:
< cache usage = "read-write" / > | CustomerOrder.hbm.xml; CustomerOffer.hbm.xml; Customer.hbm.xml CustomerAccount.hbm.xml ........ c) 在Web Application Library 目录增加一些类库: Spring-modules-cache.jar ; ehcache.jar d) 把文件 ehcache.xml 放在 WEB-INF/ehcache.xml
< ehcache> < diskStore path= "java.io.tmpdir" / > < defaultCache maxElementsInMemory= "10000" eternal= "false" timeToIdleSeconds= "120" timeToLiveSeconds= "120" overflowToDisk= "true" / > < cache name= "com.???.???.domain.CustomerOrder" maxElementsInMemory= "50" eternal= "false" timeToIdleSeconds= "100" timeToLiveSeconds= "100" overflowToDisk= "false" / > < / ehcache> | e) 对查询结果 QueryResult cache, 如下修改源码: CustomerDAO.java: ........ queryObject.setCacheable(true); return queryObject.list(); ......... 2. 如何得到 eh-cache 统计命中率: a) 在 ApplicationContext.xml增加配置:
< bean id= "hibernateSessionID" class= "org.springframework.orm.hibernate3.LocalSessionFactoryBean" > < property name= "hibernateProperties" > < props> …… < prop key= "hibernate.generate_statistics" > true< / prop> < / props> < / property> < / bean> | b) 修改package com.xxx.yyy.dao.impl中类CustmerOrderExtDAO.java的方法queryWithPaging: private List queryWithPaging(final String queryStr, final String[] paramNames, final Object[] values, final int pageNum, final int maxRowsPerPage){ …… Statistics stats =getHibernateTemplate().getSessionFactory().getStatistics(); long l2HitCount = stats.getSecondLevelCacheHitCount(); long l2MissCount = stats.getSecondLevelCacheMissCount(); long queryHitCount = stats.getQueryCacheHitCount(); long queryMissCount = stats.getQueryCacheMissCount(); System.out.println("L2_Cache_Hit :"+l2HitCount); System.out.println("L2_Cache_Miss :"+l2MissCount); double l2CacheHitRatio = l2HitCount / (l2HitCount + l2MissCount + 0.000001); System.out.println("L2_Cache_Hit_Ratio :"+l2CacheHitRatio); System.out.println(""); System.out.println("Query_Cache_Hit :"+queryHitCount); System.out.println("Query_Cache_Miss :"+queryMissCount); …… Return result } c)模拟多个客户并发访问类似的数据,我们得到很好的结果: EH cache 命中率逐渐收敛到接近75%左右。 3. Hibernate fetch strategy(solve N+1 select problem): a) 在文件 ApplicationContext.xml中增加配置:
...... < bean id= "hibernateSessionID" class= "org.springframework.orm.hibernate3.LocalSessionFactoryBean" > < property name= "hibernateProperties" > < props> …… < prop key= "hibernate.cache.use_minimal_puts" > true< / prop> < prop key= "hibernate.use_outer_join" > true< / prop> < prop key= "hibernate.max_fetch_depth" > 3< / prop> < / props> < / property> < / bean> ...... | b) 在Hibernate 映射文件中配置属性 fetch = “join”: CustomerOrder.hbm.xml; CustomerOffer.hbm.xml; Customer.hbm.xml CustomerAccount.hbm.xml ........ <set name="CustOrders" lazy="false" fetch="join" inverse="true"> <set name="orderStatuses" lazy="false" fetch="join" inverse="true" order-by="STATUS_TS desc" > <set name="orderAccounts" lazy="false" fetch="join" inverse="true"> ........ c)看结果,当我们通过以下配置打开数据库SQL query时: <prop key="hibernate.show_sql">true</prop> 我们能够看到hibernate比以前只需要更少的数据库访问次数,因为它能够在二级EHcache中找到大部分的数据。 =============================================================================== 项目需要缓存,google了一下,发现spring module有现成的东西可用,随即拿来看看。发现还是比较好用的。 改cache采用了aop的方式进行cache的写入和刷出。使用spring风格,上手容易。 系统负载不高,因此对于该缓存方法的性能等等不做评价,目前满足需要。 使用方法: 有这么几个概念需要提前知道1.XXCacheFacade,比如如果是oscache,那么XX就是oscachefacade.该类负责缓存的写入和刷出
- <bean id= "oscacheFacade" class = "org.springmodules.cache.provider.oscache.OsCacheFacade" >
- <property name="failQuietlyEnabled" value= "true" />
- <property name="cacheManager" >
- <bean id="oscacheManager" class = "org.springmodules.cache.provider.oscache.OsCacheManagerFactoryBean" />
- </property>
- </bean>
<bean id="oscacheFacade" class="org.springmodules.cache.provider.oscache.OsCacheFacade">
<property name="failQuietlyEnabled" value="true"/>
<property name="cacheManager">
<bean id="oscacheManager" class="org.springmodules.cache.provider.oscache.OsCacheManagerFactoryBean"/>
</property>
</bean>
里面的cacheManager必须要有,改类负责提供底层具体的cache实现,比如oscache或者EHcache等。 2.MethodMapCachingInterceptor这个拦截器是官方提供的同类型的拦截器之一,根据方法名,参数匹配拦截。
- <bean id= "cachingInterceptor001" class = "org.springmodules.cache.interceptor.caching.MethodMapCachingInterceptor" >
- <property name="cacheProviderFacade" ref= "oscacheFacade" />
- <property name="cachingModels" >
- <props>
- <prop key="com.company.jncz.TestItIF.get*" >groups=aa;refreshPeriod= 10 </prop>
- <prop key="com.company.jncz.TestItIF.load*" >groups=bb;refreshPeriod= 10 </prop>
- </props>
- </property>
- </bean>
<bean id="cachingInterceptor001" class="org.springmodules.cache.interceptor.caching.MethodMapCachingInterceptor">
<property name="cacheProviderFacade" ref="oscacheFacade"/>
<property name="cachingModels">
<props>
<prop key="com.company.jncz.TestItIF.get*">groups=aa;refreshPeriod=10</prop>
<prop key="com.company.jncz.TestItIF.load*">groups=bb;refreshPeriod=10</prop>
</props>
</property>
</bean>
注意cachingModels。有两种方式写法,一种是上面看到的使用props另一种是使用Map.在有些情况下只能使用Map方式,下面解释
- <map>
- <entry key="com.company.jncz.TestIt.get*" >
- <ref local="oscacheCachingModel" />
- </entry>
- </map>
<map>
<entry key="com.company.jncz.TestIt.get*">
<ref local="oscacheCachingModel"/>
</entry>
</map>
- <bean id= "oscacheCachingModel" class = "org.springmodules.cache.provider.oscache.OsCacheCachingModel" >
- <property name="groups" >
- <list>
- <value>aa</value>
- <value>bb</value>
- </list>
- </property>
- <property name="refreshPeriod" value= "10" />
- </bean>
<bean id="oscacheCachingModel" class="org.springmodules.cache.provider.oscache.OsCacheCachingModel">
<property name="groups">
<list>
<value>aa</value>
<value>bb</value>
</list>
</property>
<property name="refreshPeriod" value="10"/>
</bean>
尤其当groups(对于oscache来说是groups)的值不止一个的时候,就需要使用map的方式。 否则不生效(也许还有什么我没有注意到).另外需要注意的是对于model来说他的key很重要。有以下需要注意:如果AImpl是A接口的实现 类,而且你在其他地方使用时都是用A接口来声明的,那么key就必须写成接口的全限定名比如:com.company.jncz.A.getXX,否则无 法识别。 对于与cachingModel相对应的flushingModel写法是类似的,参考api很容易写出来。 最后
- <bean class = "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
- <property name="beanNames" >
- <value>*Service</value>
- </property>
- <property name="interceptorNames" >
- <list>
- <value>cachingInterceptor001</value>
- <value>flushingInterceptor001</value>
- </list>
- </property>
- </bean>
- <bean id="testService" class = "com.company.jncz.TestItStub" />
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<value>*Service</value>
</property>
<property name="interceptorNames">
<list>
<value>cachingInterceptor001</value>
<value>flushingInterceptor001</value>
</list>
</property>
</bean>
<bean id="testService" class="com.company.jncz.TestItStub"/>
这些不做介绍。 总之比较简单。这两天感冒,昏昏沉沉,不知道有没有表达清楚。。。。 自己看了一眼,的确没说清楚,要清楚的几个概念是 cacheFacade.CachingModel.FlushingModel.CachingInterceptor.FlushingInterceptor. 这些算是比较重要的。其他aop内容略。caching拦截器拦截某方法,根据cachingModel定义的规则由cacheFacade将之放入 cache。flushing拦截器拦截到某方法,根据flushingModel定义的规则由cacheFacade将对应的cache清除。嗯,基本 上流程就这样子了。 |