赞
踩
1、SpringCache是Spring提供的一个缓存框架,在Spring3.1版本开始支持将缓存添加到现有的spring应用程序中,在4.1开始,缓存已支持JSR-107注释和更多自定义的选项。
2、SpringCache利用了AOP,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能了,做到了对代码侵入性做小。
3、SpringCache框架还提供了CacheManager接口,可以实现降低对各种缓存框架的耦合。它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache。
1、Cache接口:缓存接口,定义缓存操作。实现有 如RedisCache、EhCacheCache、ConcurrentMapCache等2、cacheResolver:指定获取解析器
3、CacheManager:缓存管理器,管理各种缓存(Cache)组件;如:RedisCacheManager,使用redis作为缓存。指定缓存管理器
1- @Cacheable:在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用方法获取数据返回,并缓存起来。
2- @CacheEvict:将一条或多条数据从缓存中删除。
3- @CachePut:将方法的返回值放到缓存中
4- @EnableCaching:开启缓存注解功能
5- @Caching:组合多个缓存注解;
6- @CacheConfig:统一配置@Cacheable中的value值
RedisConfig 类路径: com.ruoyi.framework.config.RedisConfig
- @Slf4j
- @Configuration
- @EnableCaching //1- spring 自动管理缓存机制 ,,提升性能
- @EnableConfigurationProperties(RedissonProperties.class)
- public class RedisConfig {
- /**
- * 2-自定义缓存管理器 整合spring-cache
- */
- @Bean
- public CacheManager cacheManager() {
- return new PlusSpringCacheManager();
- }
自定义 管理器:PlusSpringCacheManager,实现CacheManager 接口,基于redssion操作缓存
- /**
- * Copyright (c) 2013-2021 Nikita Koksharov
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.ruoyi.framework.manager;
-
- import com.ruoyi.common.utils.redis.RedisUtils;
- import org.redisson.api.RMap;
- import org.redisson.api.RMapCache;
- import org.redisson.spring.cache.CacheConfig;
- import org.redisson.spring.cache.RedissonCache;
- import org.springframework.boot.convert.DurationStyle;
- import org.springframework.cache.Cache;
- import org.springframework.cache.CacheManager;
- import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
- import org.springframework.util.StringUtils;
-
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentMap;
-
- /**
- * A {@link org.springframework.cache.CacheManager} implementation
- * backed by Redisson instance.
- * <p>
- * 修改 RedissonSpringCacheManager 源码
- * 重写 cacheName 处理方法 支持多参数
- *
- * @author Nikita Koksharov
- *
- */
- @SuppressWarnings("unchecked")
- public class PlusSpringCacheManager implements CacheManager {
-
- //是否自动配置name
- private boolean dynamic = true;
-
- //是否允许null
- private boolean allowNullValues = true;
-
- //事务提交之后执行
- private boolean transactionAware = true;
-
- // 常用缓存配置 ttl; maxIdleTime; maxSize; 等
- Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
- // 缓存实例
- ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
-
- /**
- * Creates CacheManager supplied by Redisson instance
- */
- public PlusSpringCacheManager() {
- }
-
-
- /**
- * Defines possibility of storing {@code null} values.
- * <p>
- * Default is <code>true</code>
- *
- * @param allowNullValues stores if <code>true</code>
- */
- public void setAllowNullValues(boolean allowNullValues) {
- this.allowNullValues = allowNullValues;
- }
-
- /**
- * Defines if cache aware of Spring-managed transactions.
- * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
- * <p>
- * Default is <code>false</code>
- *
- * @param transactionAware cache is transaction aware if <code>true</code>
- */
- public void setTransactionAware(boolean transactionAware) {
- this.transactionAware = transactionAware;
- }
-
- /**
- * Defines 'fixed' cache names.
- * A new cache instance will not be created in dynamic for non-defined names.
- * <p>
- * `null` parameter setups dynamic mode
- *
- * @param names of caches
- */
- public void setCacheNames(Collection<String> names) {
- if (names != null) {
- for (String name : names) {
- getCache(name);
- }
- dynamic = false;
- } else {
- dynamic = true;
- }
- }
-
- /**
- * Set cache config mapped by cache name
- *
- * @param config object
- */
- public void setConfig(Map<String, ? extends CacheConfig> config) {
- this.configMap = (Map<String, CacheConfig>) config;
- }
-
- protected CacheConfig createDefaultConfig() {
- return new CacheConfig();
- }
-
- @Override
- public Cache getCache(String name) {
- // 重写 cacheName 支持多参数
- /**
- * 演示案例 : String DEMO_CACHE = "demo:cache#60s#10m#20";
- */
- String[] array = StringUtils.delimitedListToStringArray(name, "#");
- name = array[0];
-
- Cache cache = instanceMap.get(name);
- if (cache != null) {
- return cache;
- }
- //2- dynamic=false 不会动态生成
- if (!dynamic) {
- //return cache;
- return null;
- }
-
- CacheConfig config = configMap.get(name);
- if (config == null) {
- config = createDefaultConfig();
- configMap.put(name, config);
- }
-
- //setTTL
- if (array.length > 1) {
- config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
- }
- //setMaxIdleTime
- if (array.length > 2) {
- config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
- }
- //setMaxSize
- if (array.length > 3) {
- config.setMaxSize(Integer.parseInt(array[3]));
- }
-
- if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
- return createMap(name, config);
- }
-
- return createMapCache(name, config);
- }
-
- private Cache createMap(String name, CacheConfig config) {
- //1-获取缓存
- RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
- //2-没有过期时间传2个参数
- Cache cache = new RedissonCache(map, allowNullValues);
- // 3-事务提交 之后执行
- if (transactionAware) {
- cache = new TransactionAwareCacheDecorator(cache);
- }
- //4-不存在就添加
- Cache oldCache = instanceMap.putIfAbsent(name, cache);
- if (oldCache != null) {
- cache = oldCache;
- }
- return cache;
- }
-
- private Cache createMapCache(String name, CacheConfig config) {
- //1-获取缓存
- RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
- //2-有过期时间传3个参数 ,config 里面有 ttl、maxIdleTime、maxSize
- Cache cache = new RedissonCache(map, config, allowNullValues);
- // 3-事务提交 之后执行
- if (transactionAware) {
- cache = new TransactionAwareCacheDecorator(cache);
- }
- //4-不存在就添加
- Cache oldCache = instanceMap.putIfAbsent(name, cache);
- if (oldCache != null) {
- cache = oldCache;
- } else {
- map.setMaxSize(config.getMaxSize());
- }
- return cache;
- }
-
- //返回不可修改的集合
- @Override
- public Collection<String> getCacheNames() {
- return Collections.unmodifiableSet(configMap.keySet());
- }
-
-
- }
以下Cacheable几个属性分别演示了如何使用:(支持SPEL表达式)
- cacheNames
- key
- sync
- condition
- sync
- /**
- * <简述>cacheNames: 指定名称 可以是数组
- * key: 支持spel表达式,可以获取参数
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @param pageQuery
- * @return java.lang.String
- */
- @Cacheable(cacheNames = "cache1", key = "#id + '_cache' + #pageQuery.pageNum")
- @GetMapping("test1")
- public String test1(String id, PageQuery pageQuery){
-
- return "ok";
- }
-
- /**
- * <简述> condition :符合条件进行缓存
- * #id != null :表示传入 id不为空才会缓存进入redis,id为空则不缓存
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @Cacheable(cacheNames = "cache2", key = "#id + '_cache'" , condition = "#id != null")
- @GetMapping("test2")
- public String test2(String id){
-
- return "ok";
- }
-
- /**
- * <简述> unless 符合条件不缓存
- * #result == null :接口返回结果为空则不进行缓存
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @Cacheable(cacheNames = "cache3", key = "#id + '_cache'" , unless = "#result == null")
- @GetMapping("test3")
- public String test3(String id){
- return null;
- }
-
- /**
- * <简述> sync = true
- * 同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
- * 作用:防止缓存积存
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @Cacheable(cacheNames = "cache4", key = "#id + '_cache'", sync = true)
- @GetMapping("test4")
- public String test4(String id){
- return null;
- }
-
- /**
- * <简述> 获取类中参数
- * 比较繁琐,一般是在实现类中传递登录参数,用spel获取
- * @author syf
- * @date 2024/5/7 11:03
- * @return java.lang.String
- */
- @Cacheable(cacheNames = "cache5", key = "T(com.ruoyi.common.helper.LoginHelper).getLoginUser().getLoginId()")
- @GetMapping("test5")
- public String test5(){
- LoginUser loginUser = LoginHelper.getLoginUser();
- return "ok";
- }
缓存更新
执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
- /**
- * <简述> 结果不为空进行更新
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @CachePut(cacheNames = "cache2", key = "#id + '_cache'" , condition = "#result != null")
- @GetMapping("test2")
- public String test2(String id){
- boolean flag = doUpdate();
- return flag ? "ok" : null;
- }
缓存删除
执行该方法,并将缓存中结果删除。
allEntries 删除所有cacheNames = "cache4",下面缓存beforeInvocation 默认false,方法执行之后有异常不执行。true:方法执行之后有异常,也执行
- /**
- * <简述> 删除缓存
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @CacheEvict(cacheNames = "cache4", key = "#id + '_cache'")
- @GetMapping("test7")
- public String test7(String id){
- boolean flag = doDelete();
- return flag ? "ok" : null;
- }
-
-
- /**
- * <简述> 删除所有缓存
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @CacheEvict(cacheNames = "cache4", allEntries = true)
- @GetMapping("test8")
- public String test8(String id){
- return null;
- }
-
- /**
- * <简述> beforeInvocation 无论是否有异常都执行操作
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @CacheEvict(cacheNames = "cache4", beforeInvocation = true)
- @GetMapping("test9")
- public String test9(String id){
- return null;
- }
指定多个Spring Cache相关的注解
三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
- @Caching(
- cacheable = {@Cacheable(value = "uer1",key = "#userName")},
- put = {@CachePut(value = "uer1", key = "#result.id"),
- @CachePut(value = "uer1", key = "#result.age")
- }
- )
- public User getStuByStr(String userName) {
-
- List<User> users= listMapper.selectByList(studentExample);
- return Optional.ofNullable(users).orElse(null).get(0);
- }
CacheNames 缓存名称配置类:
类位置:com.ruoyi.common.constant.CacheNames
key 格式为: cacheNames#ttl#maxIdleTime#maxSize
/**
* 缓存组名称常量
* <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
* <p>
* ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0 (超过maxIdleTime LRU算法自动清理)
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* <p>
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
*/
PlusSpringCacheManager 实现 CacheManager 接口,重写 getCache 方法,
就是配置了 :ttl、maxIdleTime、maxSize 三个参数吗,如下:
- @Override
- public Cache getCache(String name) {
- // 重写 cacheName 支持多参数
- /**
- * 1-演示案例 : String DEMO_CACHE = "demo:cache#60s#10m#20";
- */
- String[] array = StringUtils.delimitedListToStringArray(name, "#");
- name = array[0];
-
- Cache cache = instanceMap.get(name);
- if (cache != null) {
- return cache;
- }
- //2- dynamic=false 不会动态生成
- if (!dynamic) {
- //return cache;
- return null;
- }
-
- CacheConfig config = configMap.get(name);
- if (config == null) {
- config = createDefaultConfig();
- configMap.put(name, config);
- }
-
- //setTTL
- if (array.length > 1) {
- config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
- }
- //setMaxIdleTime
- if (array.length > 2) {
- config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
- }
- //setMaxSize
- if (array.length > 3) {
- config.setMaxSize(Integer.parseInt(array[3]));
- }
-
- if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
- return createMap(name, config);
- }
-
- return createMapCache(name, config);
- }
重点:下面就是PlusSpringCacheManager ,操作缓存的地方
上面调用了:createMap、createMapCache 2个方法对比:
1-逻辑:
createMapCache 多了个 setMaxSize判断,其他都一样
else { map.setMaxSize(config.getMaxSize()); }2- 返回类型
createMap 返回 RMap
createMapCache 返回 RMapCache
对比: RMapCache 继承了 RMap 多了对于ttl、maxIdleTime、maxSize 的配置
相同 :都是基于redisson,缓存到redis
- private Cache createMap(String name, CacheConfig config) {
- //1-获取缓存
- RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
- //2-没有过期时间传2个参数
- Cache cache = new RedissonCache(map, allowNullValues);
- // 3-事务提交 之后执行
- if (transactionAware) {
- cache = new TransactionAwareCacheDecorator(cache);
- }
- //4-不存在就添加
- Cache oldCache = instanceMap.putIfAbsent(name, cache);
- if (oldCache != null) {
- cache = oldCache;
- }
- return cache;
- }
-
- private Cache createMapCache(String name, CacheConfig config) {
- //1-获取缓存
- RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
- //2-有过期时间传3个参数 ,config 里面有 ttl、maxIdleTime、maxSize
- Cache cache = new RedissonCache(map, config, allowNullValues);
- // 3-事务提交 之后执行
- if (transactionAware) {
- cache = new TransactionAwareCacheDecorator(cache);
- }
- //4-不存在就添加
- Cache oldCache = instanceMap.putIfAbsent(name, cache);
- if (oldCache != null) {
- cache = oldCache;
- } else {
- map.setMaxSize(config.getMaxSize());
- }
- return cache;
- }
上面 TransactionAwareCacheDecorator:
所执行的put操作,是在事务提交之后执行
- public void put(final Object key, @Nullable final Object value) {
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- public void afterCommit() {
- TransactionAwareCacheDecorator.this.targetCache.put(key, value);
- }
- });
- } else {
- this.targetCache.put(key, value);
- }
-
- }
private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class); 主要是获取 CacheManager 接口,提供对缓存CRUD操作 :
public interface CacheManager { @Nullable Cache getCache(String name); Collection<String> getCacheNames(); }
- package com.ruoyi.common.utils.redis;
-
- import com.ruoyi.common.utils.spring.SpringUtils;
- import lombok.AccessLevel;
- import lombok.NoArgsConstructor;
- import org.redisson.api.RMap;
- import org.springframework.cache.Cache;
- import org.springframework.cache.CacheManager;
-
- import java.util.Set;
-
- /**
- * 缓存操作工具类 {@link }
- *
- * @author Michelle.Chung
- * @date 2022/8/13
- */
- @NoArgsConstructor(access = AccessLevel.PRIVATE)
- @SuppressWarnings(value = {"unchecked"})
- public class CacheUtils {
-
- private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
-
- /**
- * 获取缓存组内所有的KEY
- *
- * @param cacheNames 缓存组名称
- */
- public static Set<Object> keys(String cacheNames) {
- RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
- return rmap.keySet();
- }
-
- /**
- * 获取缓存值
- *
- * @param cacheNames 缓存组名称
- * @param key 缓存key
- */
- public static <T> T get(String cacheNames, Object key) {
- Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
- return wrapper != null ? (T) wrapper.get() : null;
- }
-
- /**
- * 保存缓存值
- *
- * @param cacheNames 缓存组名称
- * @param key 缓存key
- * @param value 缓存值
- */
- public static void put(String cacheNames, Object key, Object value) {
- CACHE_MANAGER.getCache(cacheNames).put(key, value);
- }
-
- /**
- * 删除缓存值
- *
- * @param cacheNames 缓存组名称
- * @param key 缓存key
- */
- public static void evict(String cacheNames, Object key) {
- CACHE_MANAGER.getCache(cacheNames).evict(key);
- }
-
- /**
- * 清空缓存值
- *
- * @param cacheNames 缓存组名称
- */
- public static void clear(String cacheNames) {
- CACHE_MANAGER.getCache(cacheNames).clear();
- }
-
- }
概念:
缓存集中过期失效(大量key失效)。所有请求直接查询数据库了,而对数据库造成巨大压力,严重可能的会导致数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决:
- 1、实现Redis的高可用、改为主从+哨兵集群模式
- 2、允许的话,也可以设置热点数据不过期(或者不同业务设置不同过期时间 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500)
- 3、开启Redis的RDB+AOF组合持久化策略,以便快速恢复
概念:
缓存击穿指的是热点key在某个特殊的场景时间内恰好失效了,恰好有大量并发请求过来了,导致大量的请求都打到数据库上,造成数据库极大的压力,这就是缓存击穿问题。
对比缓存雪崩:
雪崩大量key失效,击穿 某几个热点key失效
解决:
互斥锁方案,保证同一时间只有一个业务线程去数据库获取数据填充到Redis中,更新缓存,未能获取互斥锁的请求,需要等待锁释放后重新读取缓存。获取成功,直接返回结果;获取失败,则再次尝试获取锁,重复上述流程。
若依框架中的实现:简单举例
sync = true 同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
- /**
- * <简述> sync = true
- * 同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
- *
- * @author syf
- * @date 2024/5/7 11:03
- * @param id
- * @return java.lang.String
- */
- @Cacheable(cacheNames = "cache4", key = "#id + '_cache'", sync = true)
- @GetMapping("test4")
- public String test4(String id){
- return null;
- }
概念:
用户在不断访问一个在缓存和数据库中都没有的数据,缓存无法命中,从而导致一直请求数据库,流量过大就会导致数据库的崩溃,这就是缓存穿透问题。
解决:
- 接口层增加校验,如用户鉴权等;
- 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;
- 将空结果(NULL)或默认查询结果存入到缓存中,并设置值过期时间。
布隆过滤器可以参考文章:
redis中布隆过滤器使用详解_redis布隆过滤器使用-CSDN博客https://blog.csdn.net/w1014074794/article/details/129750865
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。