- 前言
- 一、Spring-Cache的使用
- 1.引入依赖
- 2.写配置
- 1)先看它自动配置了哪些
- 2)修改配置文件,配置使用redis作为缓存
- 3.测试使用缓存
- 1)开启缓存功能
- 2)使用注解实现缓存功能
- 3)默认行为和自定义操作
- (1)缓存的默认行为
- (2)自定义操作
- 二、Spring-cache的原理和不足
- 1.原理
- 2.不足
- 总结
前言
上次我们简单介绍了一下本地锁和分布式锁,这次我们在看一下用redis作为缓存,并使用Spring-Cache进行整合redis作为缓存,并会分析Spring-Cache的原理及它的优缺点。
一、Spring-Cache的使用 1.引入依赖
2.写配置 1)先看它自动配置了哪些org.springframework.boot spring-boot-starter-cache
首先,我们进到CacheAutoConfiguration这个类下,发先他会导入RedisCacheConfiguration,并自动配好了缓存管理器
来到application.properties文件夹下,spring-cache的一些配置如下:
# 指定redis作为缓存 spring.cache.type=redis #spring.cache.cache-names= # 指定缓存数据的存活时间 以毫秒为单位 spring.cache.redis.time-to-live=3600000 # 如果制定了前缀,就用指定的前缀,如果没有,就用缓存的名字作为前缀 #spring.cache.redis.key-prefix=CACHE_ spring.cache.redis.use-key-prefix=true # 是否缓存空值,防止缓存穿透 spring.cache.redis.cache-null-values=true3.测试使用缓存 1)开启缓存功能
在spring boot 的启动类上加上@EnableCaching注解即可,如果我们想自定义缓存的一下信息,可以在新建一个配置类,来进行一些自定义的配置,如:将保存数据以json格式存入缓存等。这个我们将在下边的详细介绍。当有我们自定义的配置类,我们就可以将@EnableCaching注解加到配置类上,来开启缓存功能。
2)使用注解实现缓存功能常见的注解:
- @Cacheable : 触发将数据保存到缓存的操作
- @CacheEvict : 触发将数据从缓存删除
- @CachePut : 不影响方法执行更新缓存
- @Cacheing : 组合以上多个操作
- @CacheConfig : 在类级别共享缓存的相同配置
代码示例:
@Cacheable(value = {"test"},key = "#root.methodName") @Override public ListqueryAll() { return testDao.queryAll(); } // @Caching(evict = { // @CacheEvict(value = "category",key = "'getLevel1Categorys'"), // @CacheEvict(value = "category",key = "'getCatelogJson'") // }) @CacheEvict(value = "test",allEntries = true) //失效模式 // @CachePut //双写模式 @Override public void update(TestEntity testEntity) { int id = testEntity.getId(); String name = testEntity.getName(); String idno = testEntity.getIdno(); testDao.update(id, name, idno); }
@Cacheable注解中的参数,value指定缓存分区的名字,指定key的值为方法的名字;
@CachePut注解在进行数据库数据更新时,可以传入分区名字和key,将方法返回的数据更新到指定的缓存中,这是双写模式来解决缓存数据一致性问题;
@CacheEvic注解中的参数,value指定缓存分区的名字,指定key的名字,allEntries = true代表的是value指定分区中全部的key,只是用到了失效性来解决缓存数据一致性的问题;
当有更新操作涉及到多个缓存的时候,我们可以使用对同一分区下的数据进行删除,也可以使用@Caching来指定多个@CacheEvic来进行删除;
结果展示:
发现执行查询之后,查询到的数据就放到了redis中,因为我们在配置文件中配置了相关的过期时间(spring.cache.redis.time-to-live=3600000),所以展示出来的是我们对应配置的,但是到这儿存到redis中的数据格式是不是和我截图中的不一样,这个就是因为小编自定义了了缓存的配置类,稍后我会给大家展示。
上边讲到了我们可以通过配置文件或者新建配置类来自定义缓存管理器在生成缓存的时候自定义一些操作,比如过期时间,展示格式,是否指定前缀,是否缓存空值等等
(1)缓存的默认行为1)、如果缓存中存,方法不调用
2)、key默认自动生成:缓存的名字: :SimpleKey [](自动生成的key值)
3)、缓存的value的值: 默认使用jdk序列化机制,将序列化后的数据存到redis
4)、默认ttl时间: -1
我们可以看一下RedisCacheConfiguration中如果没有传指定的值,那么就会按照默认的去构建
1)、指定生成的缓存使用的key; key属性指定,接收一个SpEL;
2)、指定缓存的数据存活时间 配置文件中修改ttl;
3)、将数据保存为json格式;
前两点我们分别通过注解和配置文件解决了,第三个配置我们需要自定义一个缓存的配置类,代码如下:
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.cache.CacheProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @EnableConfigurationProperties(CacheProperties.class) @EnableCaching @Configuration public class MyCacheConfig { // @Autowired // CacheProperties cacheProperties; @Bean RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // config = config.entryTtl(); config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer())); CacheProperties.Redis redisProperties = cacheProperties.getRedis(); //将配置文件中的所有配置都让他生效 if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixCacheNameWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; } }二、Spring-cache的原理和不足 1.原理
CacheAutoConfiguration->RedisCacheConfiguration->
自动配置了RedisCacheManager->初始化所有的缓存->每个缓存决定使用什么配置
->如果RedisCacheConfiguration有就用已有的,没有就用默认配置
->想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可
->就会应用到当前缓存管理器管理的所有缓存分区中
1)、读模式:
缓存穿透:查询一个null数据。解决:缓存空数据:spring.cache.redis.cache-null-values=true
缓存击穿:大量并发同时查询一个正好过期的数据。解决:加锁:默认是无加锁的sync = true(加锁解决击穿)
缓存雪崩:大量的key同时过期。解决:加随机时间,加上过期时间
2)、写模式(缓存与数据库的一致)
1)、读写加锁。
2)、引入Canal,感知到MySql的更新去更新数据库
3)、读多写多,直接去数据库查询就行
总结
对于常规数据(读多写少,即时性和一致性要求不高的数据):完全可以使用spring-cache 写模式(只要缓存的数据有过期时间就足够了)
对于特殊数据:需要特殊设计
总体来说spring-cache进行整合redis作为缓存带来了很多方便,在读模式中他也可以很好的解决缓存穿透、缓存击穿、缓存雪崩等问题,在写模式中就要考虑配合加锁或者canal来进行缓存数据一致性的解决方案了。