关于spring cache技术调研报告
前言
缓存通俗点说就是临时存储数据的可高速访问的地方。当从原始位置获取数据的代价太大或者时间太长的时候,就可以把获取到的数据存放在缓存中,这样下次访问的时候就提高了访问速度降低了访问成本。本文就是为了合理利用缓存资源,提高艾特密服务商城响应效率。
基本情况
springcache的使用需要与redis相互配合,springcache的缓存是存在本地内存,这样会增加服务器的负担,而redis可以指定本地也可以指定其他服务器进行存储。分担本地服务器的压力。
调研过程
一.spring cache简介
Spring3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。
二.spring cache特点
●通过少量的配置 annotation 注释即可使得既有代码支持缓存
●支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
●支持 Spring ExpressLanguage,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
●支持 AspectJ,并通过其实现任何方法的缓存支持
●支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性
三.spring cache优势:
基于注解的支持
Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable、@CachePut 和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,@CachePut主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable不同的是,它每次都会触发真实方法的调用,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。
1.@Cacheable
@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。
value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个。如@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。如@Cacheable(value=”testcache”,key=”#userName”)
condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。如@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
注:除了上述使用方法参数作为key之外,spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
属性名称 | 描述 | 示例 |
methodName | 当前方法名 | #root.methodName |
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
2.@CachePut
在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。
3.@CacheEvict
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。
allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。如:@CachEvict(value=”testcache”,beforeInvocation=true)
其他参数和@Cacheable相同
4.@Caching
@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。如: @Caching(cacheable =@Cacheable("users"), evict = {@CacheEvict("cache2"),@CacheEvict(value = "cache3",allEntries = true) })
四.注意:
spring cache功能确实是带来很多方便,但是他的一些缺陷让人头疼。
1、他的cache功能是基于aop的,所以当内部调用方法的时候就不会调用cache方法。这个也不能说是spring的问题,总之你需要把内部互相调用的方法分开写然后调用接口的方式调用。
五.:redis 与memcached区别
1. Redis中,并不是所有的数据都一直存储在内存中的,这是和Memcached相比一个最大的区别。
2. Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
3. Redis支持数据的备份,即master-slave模式的数据备份。
4. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
5、分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从,目前redis已经支持集群了。http://docs.spring.io/spring-data/redis/docs/current/reference/html/#cluster
6、存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)。
7、灾难恢复--memcache挂掉后,数据不可恢复;redis数据丢失后可以通过aof恢复。
8、memcache数据结构单一;redis丰富一些,数据操作方面,redis更好一些,较少的网络IO次数。
六.在windows上部署使用Redis下载Redis
地址:http://www.cnblogs.com/smileyearn/articles/4749746.html
配置地址:http://www.open-open.com/lib/view/open1418105514058.html
七.spring cache与redis缓存整合
1.pom添加
这里增加spring-data-redis 和jedis 必须要jar包。
<spring-data-redis.version>1.4.2.RELEASE</spring-data-redis.version>
<jedis.version>2.6.0</jedis.version>
.
.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
2.spring 配置
在spring的配置文件applicationContext-cache.xml里加入下面的redis的配置信息。
<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache=http://www.springframework.org/schema/cache
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
<beanid="poolConfig"class="redis.clients.jedis.JedisPoolConfig">
<propertyname="maxIdle"value="${redis.maxIdle}"/>
<propertyname="maxTotal"value="${redis.maxActive}"/>
<propertyname="maxWaitMillis"value="${redis.maxWait}"/>
<propertyname="testOnBorrow"value="${redis.testOnBorrow}"/>
</bean>
<beanid="connectionFactory"class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<propertyname="poolConfig"ref="poolConfig"/>
<propertyname="port"value="${redis.port}"/>
<propertyname="hostName"value="${redis.host}"/>
<propertyname="password"value="${redis.pass}"/>
<propertyname="timeout"value="${redis.timeout}"/>
</bean>
<beanid="redisTemplate"class="org.springframework.data.redis.core.RedisTemplate">
<propertyname="connectionFactory"ref="connectionFactory"/>
</bean>
<beanid="cacheManager"class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-argname="template"ref="redisTemplate"/>
<propertyname="cacheNames">
<set>
<value>default</value>
<value>hotProduct</value>
<value>product</value>
</set>
</property>
<propertyname="defaultExpiration"value= "6000">
</property>
</bean>
这里配置redis的一些信息,当然也可以用配置文件来配置config-dev.properties或其他环境的配置文件中)
#redissetting begin
redis.maxActive=300
redis.maxIdle=20
redis.maxWait=5000
redis.timeout=2000
redis.host=localhost
redis.port=6379
redis.pass=
redis.testOnBorrow=true
#redis setting end
这里面配置的product和 hotProduct分别是redis缓存的名称。
3.redis缓存配置类
这里配置文件中实现的RedisCacheManager,这里主要对redis的业务的操作方法。是框架自带的管理缓存的工具,并且框架中附带一个工具类的代码,主要对开发中object和list对象的序列化和反序列化。
因为redis不知object和泛型,所有在将对象存入redis时,需要将缓存的数据序列化。
4.缓存service方法
对指定的service方法做缓存,使用方法如下:
package xxxxxxxxxxxxxxxxxxxx;
import java.util.List;
/**
*
*/
@Service("hotProductService")
@Transactional
public class HotProductServiceImpl implements HotProductService {
@Autowired
private HotProductDao hotProductDao;
/**
* 添加一条
* @param hotProduct
* @return
*/
@Override
@CacheEvict(value =" hotProduct ",allEntries = true)
public int insert(HotProduct hotProduct) {
return hotProductDao.insert(hotProduct);
}
@Override
@CacheEvict(value =" hotProduct ",allEntries = true)
public int delete(HotProduct hotProduct) {
return hotProductDao.delete(hotProduct); }
/**
* 多条删除
* @param ids
* @return
*/
@Override
@CacheEvict(value =" hotProduct ",allEntries = true)
public int deletes(String[] ids) {
for(String id : ids) {
hotProductDao.deleteById(Integer.parseInt(id));
}
return 1;
}
/**
* 更新
* @param hotProduct
* @return
*/
@CacheEvict(value ="hotProduct",allEntries = true)
@Override
public int update(HotProduct hotProduct) {
return hotProductDao.update(hotProduct);
}
/**
* 查询单条
* @param hotProduct
* @return
*/
@Override
@Cacheable(value = "hotProduct",key = "'front.hotProduct.selectById_'+#hotProduct.id+'.'+#hotProduct.productId")
public HotProduct selectOne(HotProduct hotProduct) {
return hotProductDao.selectOne(hotProduct);
}
@Override
public HotProduct selectById(String id) {
return null;
}
/**
* 分页查询
* @param hotProduct
* @return
*/
@Cacheable(value = "hotProduct",key = "'front.hotProduct.selectPageList_'+#hotProduct.offset+'.'+#hotProduct.pageSize+'.'+#hotProduct.productName")
@Override
public PagerModel selectPageList(HotProduct hotProduct) {
return hotProductDao.selectPageList(hotProduct);
}
@Override
public List<HotProduct> selectList(HotProduct hotProduct) {
return null;
}
/**
* 查询列表
* @param count
* @return
*/
@Cacheable(value = "hotProduct",key = "'front.hotProduct.selectList_'+#count")
@Override
public List<HotProduct> selectList(Integer count) {
return hotProductDao.selectList(count);
}
}
4.运行结果示例
未生成cache前的redies客户端可视化窗口显示如下:
第一次访问后,生成cache的redies客户端可视化窗口显示如下:
第一次走数据库查询的速度与第二次走缓存的速度差如下图所示:
结论:使用缓存进行查询的时候,效率更加的高。
命名准则及其他详细介绍:
1、 这上面的方法就是spring配置文件里配置的两个中的一个做的缓存,这里主要是对hotProduct增删改查做的缓存。上面的方法只是hotProduct的缓存,过期时间可以在配置文件中设置。product同理。
2、 这里的key是在redis所对应的标识,如果要查询出来可以使用key值来查询。其他操作,例如对缓存进行修改和删除,需要使用@CachePut
和 @CacheEvict
,key在本项目中的命名准则是,依据xml的sql文的名字来命名的,后边加具体每次传的参数(名字参数直接用“_”分割,参数直接用“,”分割。并且参数前要用“#”标明)。
@Cacheable(value ="hotProduct",key ="'front.hotProduct.selectPageList_'+#hotProduct.offset+'.'+#hotProduct.pageSize+'.'+#hotProduct.productName")
<delete id="front.hotProduct.deleteById" parameterType="java.lang.Integer">
delete from t_hot_service where 1=1 and id=#{id}
</delete>
3、测试缓存是否成功的标准是,第一次访问可以直接查询数据库,第二次相同的访问取缓存内容,不进入数据库查询。具体可以打个断点试试,这里就不详细说明了,有关于spring cache 与redies具体可以查看官网,地址在最下方标明了。
结论
spring cache,基本能够满足一般应用对缓存的需求,但现实总是很复杂,当你的用户量上去或者性能跟不上,总需要进行扩展,这个时候你或许对其提供的内存缓存不满意了,因为其不支持高可用性,也不具备持久化数据能力,这个时候,你就需要自定义你的缓存方案了.具体可以参考springcache与redis缓存整合。总体来说是可以它的实现可以优化访问数据的速度。