spring集成ehcache(注解使用,非hibernate集成,顺便谈谈自己的看法)

ehcache是一个纯Java进程的缓存框架,和memcache不同,memcache服务端是C写的,不过ehcache好像效率比memcache高一些。当然性能上我没做过太多比较,也是听大家云云,况且这个框架也是经人介绍使用,在这里我也只是说说在web开发中如何快速搭建使用这个框架。

PS:下面的引题仅仅是个人通过真实需求遇到的问题,才考虑使用ehcache,纯属和大伙儿交流探讨经验,需要快速开发的跳过即可。

一、引题

开篇前引入我用这个框架的需求来由。项目中有一个需求是app的首页需要从后台数据库拉取用户列表(这个操作叫做首页推荐),一开始推荐要求很低,就是按一定条件推荐就好了,后来加了要求,按照一个规则进行排序。我在服务器端做了分页(hibernate分页),如果我按照页数分页反馈数据的话就造成了这么一个问题:排序仅仅能维持在一页上是准确的,在宏观上看把所有页合起来,数据就不是顺序的了(比如第一页里面得分最高的人是80分排在第一,但是有可能第二页有得分90的人呢?简单的分页排序就把第二页这个人搁置到第二页的第一个了,也就在第一页那个80分的人后面了)。这是问题的开始,直到上个礼拜新需求又来了,需要首页推荐至少100人,那么这100人怎么做处理?我难不成每次请求判断是不是到了尾页然后补齐100人(因为中间还有些许业务逻辑比较琐碎就不提了)?这样我的业务逻辑无形就复杂了很多,日后推荐需求再有变化我又如何下手呢?所以脑海中想到的一个方案就是:干脆我不在dao那块分页,某用户第一次访问的时候全取出来,数据放在一个地方,他第二次第三次访问,我从那个地方取出来一页大小的数据不就得了;而且这样做用户数据有负担的地方仅在第一次访问,后面再刷新数据则无需访问数据库,这样的开销相比分页少取数据但是每次都取也差不多。所以就顺藤摸瓜找到了cache,当然前段时间做了负载均衡,本来是想用memcache,但是问了些大牛,他们提到memcache通常用来处理静态数据等等,就把目标转向了ehcache。

二、Spring-ehcache配置

啰嗦完了,开始配置。Spring的基本配置我不提。

网上有很多教程跟我的用法不一样,我是注解使用,没有cache相关的类和我的代码直接耦合,所以配置可能有一点差异。有的需要你下载ehcache的jar包,或者maven依赖一些库,不过我这里的spring是4,在spring-context-support 或者spring-context下就有ehcache模块,截图给大家看看:

201938_4bnN_2320871.png

注意我这里是4,3有没有我还真不太清楚了。

maven项目的话依赖加上这些即可

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>4.2.1.RELEASE</version>
</dependency>

当然其他配置方法,就直接依赖 ehcache-core这个库

 <dependency>
     <groupId>net.sf.ehcache</groupId>
     <artifactId>ehcache-core</artifactId>
     <version>2.1.0</version>
 </dependency>

还有的教程会叫大家下载一个 spring-annotation-ehcahe 的库,虽然我没用到,但是这里我也给出来

<dependency>
 <groupId>com.googlecode.ehcache-spring-annotations</groupId>
 <artifactId>ehcache-spring-annotations</artifactId>
 <version>1.2.0</version>
</dependency>

googlecode,呵呵。。。,有条件的去下吧,没条件还想用的,直接下载jar用好了,百度上有很多去找找吧。

总之本教程只需要spring-context 4.*即可使用。

然后我们需要一个ehcache.xml文件去配置缓存,我写一个然后讲讲。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
 <defaultCache    
  maxElementsInMemory="1000"    
  eternal="false"    
  timeToIdleSeconds="120"    
  timeToLiveSeconds="120"    
  overflowToDisk="true" /> 
 <cache name="com.tonghang.web.user.cache.UserCache.getRecommendCache"  
     eternal="false"    
     maxElementsInMemory="3000"   
     overflowToDisk="false"      
     timeToIdleSeconds="180"   
     timeToLiveSeconds="300"    
     memoryStoreEvictionPolicy="LRU" />
</ehcache>

 

上面的dtd直接复制走即可。第一个元素defaultcache显而易见就是一个默认的cache,大家可以把cache理解为spring中的bean,一个cache标签代表一个缓存对象。默认缓存和cache配置差不多。下面说下属性。

cache中有一个name,表示缓存的名字,可以理解为命名空间,不要重复。使用了类名+方法名

eternal :英文意思永久的,也就是说这个布尔值为true代表你的缓存在程序启动被spring管理后,这个缓存一旦被创建就会永久存在,直到程序结束内存释放(注意缓存都是放在内存的,而且永久存在不代表你没法删除它)。这个属性和后面timeToIdleSeconds,timeToLiveSeconds这俩属性有关系。

maxElementsInMemory 表示这个缓存最大能容纳多少对象,0代表的是无限制。当然要慎用这个值,否则服务器内存开销会很大。这一点和memcache使用类似(不过memcache好在可以请求其他主机上的memcache再缓存,ehcache的话没有服务端,可能还需要自己写,麻烦点)。

overflowToDisk 这个属性为true 则表示缓存溢出(缓存对象超过了你上面设置的数量)即可将溢出的部分放到硬盘中,这里我配置的是false,如果是true则还需要配置一个diskStore元素,用来指定溢出部分存在哪里

<diskStore path="d:/cache" />

下面两个属性都是设置缓存的存活时间的,策略不同

timeToIdleSeconds: 表示在你最后一次使用了缓存后,再过N秒不使用,就会失效

timeToLiveSeconds: 表示缓存一共能存活的时间。

例如你如下配置:

imeToIdleSeconds=120;

timeToLiveSeconds=180;

上面的表示此缓存最多可以存活3分钟,如果期间超过2分钟未访问 那么此缓存失效。

memoryStoreEvictionPolicy:这个属性用来指定缓存清除策略,有点长,我不自己打,复制一个人说的。

 

缓存的3 种清空策略 :
FIFO ,first in first out (先进先出).
LFU , Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。
LRU ,Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

 

如果你需要配置多个cache就设置多个标签,别忘了name不要重复。

 

然后就是spring集成ehcache的配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/cache 
        http://www.springframework.org/schema/mvc 
   ">
     <cache:annotation-driven cache-manager="ehcacheManager"/>
     <!-- cacheManager工厂类,指定ehcache.xml的位置 -->
     <bean id="ehcacheManagerFactory"    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml" />
     </bean>
    <!-- 声明cacheManager -->
     <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcacheManagerFactory" />
     </bean>
    </beans>

并不是配完这一点项目就结束了,hibernate和事务我就不写在这里了免得混淆视听。如上配置即可使spring管理ehcache。 

三、代码中使用spring集成好的ehcache,全注解使用

缓存是键值对存在的,一个缓存中存在多个键值对(也就是缓存对象),我的策略是缓存命名用类名和方法名。

缓存通常是放在取数据的方法上,比如select**啊find**还有get**等方法上,因为我的业务逻辑上除了取数据,还要对数据排序、加标记等等处理,因此我需要把取数据的部分分离出来,专门形成一个缓存类,这个类的所有方法只做一个事儿,就是调用dao取数据然后再对数据进行加工。下面是我的缓存类:

@Component("userCache")
public class UserCache {
 @Resource(name="userUtil")
 private UserUtil userUtil;
 @Resource(name="userDao")
 private UserDao userDao;
 @Resource(name="labelDao")
 private LabelDao labelDao;
 
 /**
  * 业务功能:缓存UserService类中 getRecommendCache 方法的值,全部缓存。外部通过分页截取部分缓存结果
  * @param client_id
  * @param byDistance
  * @return
  * notice: value是缓存的名字,key是指定的缓存下的键名
  */
 @Cacheable(value="com.tonghang.web.user.cache.UserCache.getRecommendCache",key="#client_id+#byDistance")
 public List<Map<String,Object>> getRecommendCache(String client_id,boolean byDistance){
  .....
 }
 
 /**
  * 业务功能:缓存UserService类中 searchLabel 方法的值,全部缓存。外部通过分页截取部分缓存结果
  * @param client_id
  * @param label_name
  * @param byDistance
  * @return
  */
 @Cacheable(value="com.tonghang.web.user.cache.UserCache.getSearchLabelCache",key="#client_id+#byDistance")
 public List<Map<String,Object>> getSearchLabelCache(String client_id,String label_name,boolean  byDistance){
  .....
}
 
 @Cacheable(value="com.tonghang.web.user.cache.UserCache.getSearchNickNameCache",key="#client_id+#byDistance")
 public List<Map<String,Object>> getSearchNickNameCache(String client_id,String username,boolean byDistance, int page){
  ........
  }
 /**
  * 业务功能:抽取出来的修改用户信息功能,修改信息时将搜索和首页推荐的缓存数据清空
  * @param birth
  * @param city
  * @param sex
  * @param username
  * @param client_id
  * @return
  * notice:allEntries表示删除这个缓存的所有键对应的值(即清空缓存)
  */
 @CacheEvict(value=
  {"com.tonghang.web.user.cache.UserCache.getSearchLabelCache",
   "com.tonghang.web.user.cache.UserCache.getRecommendCache",
   "com.tonghang.web.user.cache.UserCache.getSearchNickNameCache"
  },allEntries = true)
 public Map<String,Object> evictUpdateCache(String birth,String city,String sex,String username,String client_id,boolean img){
 .......
 }
 
 @CachePut(value=
  {"com.tonghang.web.user.cache.UserCache.getSearchLabelCache",
   "com.tonghang.web.user.cache.UserCache.getRecommendCache",
   "com.tonghang.web.user.cache.UserCache.getSearchNickNameCache"
  },key="#client_id")
 public Map<String,Object> evictUpdateLabelCache(String client_id, List<String> list){
 .....
 }
 
}

我在这里只写一段有业务逻辑的方法,怕大家看烦了。@Cacheable这个注解就是用来标注 这个方法是可以被缓存的!缓存这个方法怎么理解呢?实际上是缓存了这个方法的返回值。

这个注解意思是:访问该方法时检查你设置的缓存的键是否存在,不存在则执行方法,方法结束就把返回值放到这个cache的key上;存在则不执行方法,直接将缓存中的值返回给client.

大家注意看这个注解的属性:

value就是缓存的name,这里name可以是个数组,也就是说你可以指定多个缓存在一个方法上,达到多个缓存去缓存这个方法的值。

刚刚也说过ehcache是把数据存在某个缓存的键值对中的,所以key属性就是用来设置缓存键的,这里大家可以用任意字符串,但是spring中提供SpringEL表达式,能够获取方法中的参数达到动态设置缓存key的效果。当然还有很多SpEl上下文数据。

springEl在cache中的使用规则如下:

 

名字位置描述示例
methodNameroot对象当前被调用的方法名#root.methodName
methodroot对象当前被调用的方法#root.method.name
targetroot对象当前被调用的目标对象#root.target
targetclassroot对象当前被调用的目标对象类#root.targetClass
argsroot对象当前被调用的方法的参数列表#root.args[0]
cacheroot对象当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache#root.caches[0].name
argument name执行上下文当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数#user.id

 

result执行上下文方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,'cache evict'的beforeInvocation=false)#result

 

在这里我选择了用 用户的id作为cache的ID,因为我用到缓存的地方只有和用户查询或者首页推荐有关,所以每次请求一定有用户id,其他策略大家可以跟需求来切换使用,也可以用 + 符号来组合字符串达到组合使用效果。比如我的首页推荐包括是否按照地理位置排序,所以按位置排序得到的结果肯定和不按的结果不同,所以不能仅仅靠用户的ID,需要加上另一个参数来一起制约。

 

网上还看到一个方法是自己实现key生成策略,自己实现KeyGenerator接口,这里我就不用了。

condition属性中值的设置方法和key类似用SpEl即可,这个表示的是条件缓存,即达到你指定的条件才会缓存,比如你可以指定 #name!=null 表示用户名不是空才缓存。这个注解配置好后你会发现,第一次访问这个方法后,下次再访问这个方法就不会走方法体了,直接把上次缓存的这个方法的返回值给你。这也就达到了我一开始提出的问题的解决方法,把所有数据集中到缓存中,之后访问缓存而不是数据库,速度会快很多。

 

介绍下一个注解前大伙儿发现什么问题没?缓存的好处是减少数据库访问频率,但是一旦数据库发生修改怎么办?

和Cacheable类似的是@CachePut,这个注解是在update等 更新数据的时候用的,如果你的方法有缓存值,你可以把你将要修改的信息放到方法的缓存值,那么执行这个方法的时候,会根据你设置的缓存和键来对一个具体的缓存对象进行更新,把你指定的缓存内容更新成这个方法的返回值。注解属性和@Cacheable一样的不多解释了。

@CacheEvict 这个注解是删除缓存的,可以指定多个value用来一次删除多个缓存中的所有key,或者你直接指定allEntries = true,删除全部的缓存。删除策略在配置文件中有指定,根据需求设置删除策略即可。

 

关于ehcahce的配置和使用就到这里,下面附一张图,JMater压力测试,大家可以看看测试报告就知道ehcache的厉害之处:

213008_LAwz_2320871.png

双线程访问服务器(量很少了我的笔记本性能不咋地跑多了会很卡,这里意思一下),你会发现两个线程在第一次访问的时候都花了5600~6000ms的时间,原因是第一次走到cache方法时发现还没有缓存,需要先从数据库取数据,然后把方法返回值加到缓存中;而后面的访问时间均不超过100ms,最低才20ms,效率提高了50倍!!还是蛮不错的,后期再加上nginx+tomcat+memcache负载均衡后效果也许会更好。当然诟病就是第一次访问可能会卡的要死,app同时被大量用户刷屏的时候,并发上来了就不好住,需要配置更多负载均衡来扛压力。

 

ehcache和spring的整合使用就介绍到这里。关于负载均衡的配置可以看我前面的博客。若博客中有错误,毕竟新手,还请看官们指正!

转载于:https://my.oschina.net/rpgmakervx/blog/508602

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值