在Spring框架内我们首选Spring Cache作为缓存框架的门面,之所以说它是门面,是因为它只提供接口层的定义以及AOP注解等,不提供缓存的具体存取操作。缓存的具体存储还需要具体的缓存存储,比如EhCache 、Redis等。Spring Cache与缓存框架的关系有点像SLF4j与logback、log4j的关系。
所以,比如我们自己开发一个小博客,自己的服务器又没有很多的资源独立部署redis服务,用EHCache作为缓存是比较好的选择。如果是企业级用户量,使用redis独立部署的服务作为缓存是更好的选择。
通过上一小节的学习,可以使用Spring cache通过注解的方式来操作缓存,一定程度上减少了程序员缓存操作代码编写量。注解添加和移除都很方便,不与业务代码耦合,容易维护。 这一部分内容是没有变化的,所以我们仍然使用Spring Cache
第一步:pom.xml 添加 Spring cache 和 Ehcache的 jar 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
第二步:添加入口启动类 @EnableCaching 注解开启 Caching,实例如下。
@EnableCaching
在Spring Boot中通过@EnableCaching
注解自动化配置合适的缓存管理器(CacheManager
),Spring Boot根据下面的顺序去侦测缓存提供者,也就是说Spring Cache支持下面的这些缓存框架:
第三步:添加ehcache配置
yml配置
spring:
cache:
type: ehcache
ehcache:
config: classpath:/ehcache.xml
在 resources 目录下,添加 ehcache 的配置文件 ehcache.xml ,文件内容如下:
<ehcache>
<diskStore path="java.io.tmpdir/cache_dongbb"/>
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />
<cache name="user_detail" maxElementsInMemory="10000" eternal="true" overflowToDisk="true" diskPersistent="true" diskExpiryThreadIntervalSeconds="600"/>
</ehcache>
配置含义:
缓存的使用方法仍然是Spring Cache的注解,使用方法是一样的,参考上一小节学习。
注意:@Cacheable 注解在对象内部调用不会生效。这个坑不是单独针对EhCache的,只要使用Spring Cache都会有这个问题。
@Component
public class ClassA {
@Override
public void MethodA(String username) {
MethodA1(username); //缓存失效,@Cacheable 注解在对象内部调用不会生效
}
@Cacheable(value = USER_DETAIL,key = "#username")
public void MethodA1(String username) {
//执行方法体
}
}
原因: Spring 缓存注解是基于Spring AOP切面,必须走代理才能生效,同类调用或者子类调用父类带有缓存注解的方法时属于内部调用,没有走代理,所以注解不生效。
解决办法: 将缓存方法,放在一个单独的类中
@Component
public class ClassA {
@Resource
ClassB classB;
@Override
public void methodA(String username) {
classB.methodA1(username); //缓存失效,@Cacheable 注解在对象内部调用不会生效
}
}
@Component
public class ClassB {
@Cacheable(value = USER_DETAIL,key = "#username")
public void methodA1(String username) {
//执行方法体
}
}
正常情况下,我们去查询数据大部分都是存在的。如果请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去,造成对后端数据库的强大压力。这种查询不存在数据的现象我们称为缓存穿透。(有可能会是某些不法份子的恶意行为,多线程打满去向服务查询不存在的数据)
解决办法
在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿
。
比如:鹿晗宣布恋情,导致微博瘫痪。就有可能是缓存击穿导致的,大家都去看这一个热点新闻,热点新闻的缓存如果超时失效了,就造成后端服务压力增大,服务器瘫痪。(当然这只是我猜的,举例而已)
解决办法
@Cacheable(value = CACHE_OBJECT,key = "#id",sync=true)
public ArticleVO getArticle(Long id) {
同一时刻大量缓存失效,导致请求集中的全部打到数据库。比如:双十一零点搞活动,为了支撑这次活动,事先已经缓存好大量的数据。如果所有的数据全是缓存24小时,那24小时之后这些数据缓存将集中失效,最终结果就是11.12号服务崩溃。
解决办法
可以通过准确的监控热点流量,及时的针对热点服务及缓存组件进行自动化的扩容。
不同缓存的失效时间不能一致,同一种缓存的失效时间也尽量随机(最小值–>最大值)
读写加锁
引入中间件Canal,感知到mysql的更新去更新
读多写多的,直接去数据库查询
在 application.yml指定 spring.cache.type=redis。
spring:
cache:
type: redis
redis:
cache-null-values: true # 缓存null,防止缓存穿透
use-key-prefix: true # 是否使用缓存前缀
key-prefix: boot-launch # 缓存前缀,缓存按应用分类
time-to-live: 3600 # 缓存到期时间,默认不主动删除永远不到期
其中值得注意的一点是,Spring Cache默认只支持全局对所有的缓存配置生效时间,不支持对缓存的生效时间分类配置,容易造成缓存雪崩。
由于redis缓存设置的到期时间是统一的,没有办法根据缓存名称(value属性)分别设置缓存到期的时间,容易造成缓存雪崩。所以我们进行一个简单的改造。在改造之前我们先来看一下RedisCacheManager源码
RedisCacheManager构造函数包含三个参数
RedisCacheWriter
这个在之前的章节我们就配置过RedisCacheConfiguration defaultCacheConfiguration
这个是默认的全局配置,针对所有缓存Map<String, RedisCacheConfiguration> initialCacheConfigurations
这个是针对某一种缓存的个性化配置,泛型String是缓存名称,泛型RedisCacheConfiguration
是该缓存的个性化配置理解了上面的源码,下面的改造代码就不难理解了。
@Data
@Configuration
@ConfigurationProperties(prefix = "caching") //application.yml配置前缀
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//序列化重点在这四行代码
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//从这里开始改造
//自定义redisCacheManager
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
this.buildRedisCacheConfigurationWithTTL(redisTemplate,RedisCacheConfiguration.defaultCacheConfig().getTtl().getSeconds()), //默认的redis缓存配置
this.getRedisCacheConfigurationMap(redisTemplate)); //针对每一个cache做个性化缓存配置
return redisCacheManager;
}
//配置注入,key是缓存名称,value是缓存有效期
private Map<String,Long> ttlmap; //lombok提供getset方法
//根据ttlmap的属性装配结果,个性化RedisCacheConfiguration
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(RedisTemplate redisTemplate) {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
for(Map.Entry<String, Long> entry : ttlmap.entrySet()){
String cacheName = entry.getKey();
Long ttl = entry.getValue();
redisCacheConfigurationMap.put(cacheName,this.buildRedisCacheConfigurationWithTTL(redisTemplate,ttl));
}
return redisCacheConfigurationMap;
}
//根据传参构建缓存配置
private RedisCacheConfiguration buildRedisCacheConfigurationWithTTL(RedisTemplate redisTemplate,Long ttl){
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
.entryTtl(Duration.ofSeconds(ttl));
}
}
在 application.yml指定 缓存名称对应的缓存生效时间,单位为秒
caching:
ttlmap:
article: 10
xxx: 20
yyy: 50
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_53157173/article/details/121691474
内容来源于网络,如有侵权,请联系作者删除!