spring-cache
前言
我们为什么要用缓存?为什么要用spring-cache框架?
缓存可以帮助提高系统的速度高性能,高并发,将一些复杂很耗时又很少改变但是又会经常读的操作查询出来的结果放入缓存中,下次请求直接从缓存中拿数据,效率高效
系统中把数据放入缓存的操作太多,写的代码重复性高,我们可以使用spring-cache缓存框架解决
每次调用需要缓存的方法时,spring会检查缓存中有没有指定参数指定方法是否已经调用过了,如果有则直接从缓存中拿数据返回,如果没有则执行方法并缓存结果后将数据返回,下次调用直接从缓存中取
准备工作
spring-cache中有很多缓存技术可以选择,我们这次要使用redis作为缓存技术,首先我们需要先redis的配置先准备好
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
spring:
redis:
port: 6379
host: 127.0.0.1
password: xxxx
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置文件中能配置的属性在CacheProperties中可查看
帮我们做了哪些自动配置
CacheAutoConfiguration会帮我们导入RedisCacheConfiguration的配置
在RedisCacheConfiguration中自动帮我们配置了缓存管理器
如果在配置文件中配置了cacheNames的话,就会将缓存根据配置的业务名称进行分区,在使用注解时指定数据放在那个区内
在RedisCacheConfiguration中可以看出默认是使用jdk序列化value,默认使用前缀,默认缓存null值
开始使用
在配置文件中配置我们使用的是redis作为缓存技术
spring:
cache:
type: redis
在启动类上开启缓存@EnableCaching
注解:
@Cacheable(value = “category”)
触发将数据缓存的操作,其中value表示存到什么区
@CacheEvict
触发将数据从缓存中删除的操作
@CachePut
不影响方法执行更新缓存
@Caching
组合以上多个操作
@CacheConfig
在类级别共享缓存的相同配置
使用@Cacheable保存数据结果
可以看出cache默认默认行为
一.如果缓存中有,方法不再调用
二.缓存的key值,默认是simplekey[]
三.数据序列化默认使用的jdk序列化机制
四.默认过期时间:-1(不过期)
怎样改变这些默认行为呢?
指定缓存的key: @Cacheable(value = “category”,key = “#root.methodName”)
其中key是一个SPEL表达式,如果使用普通的字符串,则需要 ‘xxxxx’
缓存过期时间
在配置文件中指定spring.cache.redis.time-to-live 单位为ms
使用JSON序列化机制则需要自定义RedisCacheConfiguration
自定义RedisCacheConfiguration
如果的返回结果是放入容器中的那么方法的参数可以从容器中获取的,所以我们只需要配置一个自定义的RedisCacheConfiguration放入容器中
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
CacheProperties.Redis redis = cacheProperties.getRedis();
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
defaultCacheConfig = defaultCacheConfig.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return defaultCacheConfig;
}
使用jackson作为序列化机制,经过测试后发现value值变成了json格式了,但是缓存时间失效了,没有读取到配置文件中的配置
我们看到源码中是从cacheProperties.getRedis()获取到配置,但是我们看到属性配置的类没有放入容器中,我们只需要使用@EnableConfigurationProperties({CacheProperties.class})开启属性配置的绑定功能,当一个方法的返回值是放入容器中的,那么这个方法的参数可以从容器中寻找,所以使用参数的形式传进来,然后将这部分代码拷贝过来即可。
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
CacheProperties.Redis redis = cacheProperties.getRedis();
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
defaultCacheConfig = defaultCacheConfig.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
if (redis.getTimeToLive() != null) {
defaultCacheConfig = defaultCacheConfig.entryTtl(redis.getTimeToLive());
}
if (redis.getKeyPrefix() != null) {
defaultCacheConfig = defaultCacheConfig.prefixCacheNameWith(redis.getKeyPrefix());
}
if (!redis.isCacheNullValues()) {
defaultCacheConfig = defaultCacheConfig.disableCachingNullValues();
}
if (!redis.isUseKeyPrefix()) {
defaultCacheConfig = defaultCacheConfig.disableKeyPrefix();
}
return defaultCacheConfig;
}
自定义就完成了。
数据一致性
失效模式:
@CacheEvict(value = "category",key = "'getCategoryOneLevel'")
// 其中value指的是删除哪一块区的,key指的是删除哪个key,也是传入一个SPEL表达式,字符串需要使用'xxx '
如果有这样一种情况,就是改变数据后需要删除两个key
方式一. 使用@Caching组合操作
@Caching(
evict = {
@CacheEvict(value = "category", key = "'getCategoryOneLevel'"),
@CacheEvict(value = "category", key = "'getIndexListCache'")
}
)
方式二. 使用@CacheEvict(value = “category”) 删除整个区的内容
双写模式:将返回的结果保存到缓存中
@CachePut(value = "category", key = "'getIndexListCache'")
总结
SpringCache的读模式的问题
1.缓存穿透:大量请求访问一个不存在的数据,导致所有请求查询数据库
springCache解决方案:spring.cache.redis.cache-null-values: true 保存返回空的数据
2.缓存雪崩:大量key同一时间失效,需要设置缓存失效随机值
springCache解决方案:spring.cache.redis.time-to-live: 36000000 设置过期时间,但是不是随机时间
3.缓存击穿:大量请求到达前一秒key失效,只让第一个请求从数据库查询吗,其他请求排队,之后从缓存中查询
springCache解决方案:
springCache默认是没有加锁的,需要手动指定sync = true
```
@Cacheable(value = "category",key = "#root.methodName",sync = true)
```
但是加的锁是本地锁,也没有保证多个服务是调用一次数据库,比如10个相同服务,最多也查询10次数据库,对数据库压力还是可以接受的。
SpringCache的写模式都没有进行加锁处理,有可能会出现暂时性的脏读,所以我们对一些数据实时性一致性不高的数据使用spring-cache就够了,只要设置失效时间,就可以在一定时间后拿到最新的数据,如果对数据实时性一致性高的数据,可以使用Redission加分布式读写锁或者可以使用canal订阅binlog的方式解决数据一致性问题。
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache)
写模式(只要缓存的数据有过期时间就足够了)