springcache

springcache 缓存集成对比 介绍了 caffeine / encache/guava等几种缓存实现 ,并介绍了几种缓存的集成.

spring中文网springcache 入门教程 介绍了spring和 springboot的集成方式.

Spring cache详解

集成

引入依赖

<!--        该 Starter 包含了 spring-context-support 模块。-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <!--        springcache  缓存包,  其中包含了 caffeine 和  encache的集成-->
    <!--        <dependency>-->
    <!--            <groupId>org.springframework</groupId>-->
    <!--            <artifactId>spring-context-support</artifactId>-->
    <!--            <version>5.1.6.RELEASE</version>-->
    <!--        </dependency>-->
    <!--  除了导入support包,当然还得导入咖啡因的包      -->
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
        <!-- 2019.2最新版本 caffeine是2015年才面市的,发展还是很迅速的-->
        <version>2.7.0</version>
    </dependency>

开启缓存

只需在任何配置类中添加 @EnableCaching 注解,即可启用缓存功能:

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("addresses");
    }
}

注意:在spring中启用缓存后,必须注册一个 cacheManager,这是最基本的设置。

而在使用 Spring Boot 时,只需在 classpath 上存在 Starter 依赖,并且与 @EnableCaching 注解一起使用,就会注册相同的 ConcurrentMapCacheManager,因此不需要单独的 cacheManage 声明

配置cacheManage

如果要使用其它的缓存框架,应该怎么做呢?

如果要使用其它的缓存框架,我们只需要重新定义好CacheManagerCacheResolver这两个Bean就行了。

事实上,Spring会自动检测我们是否引入了相应的缓存框架,如果我们引入了spring-data-redis,Spring就会自动使用spring-data-redis提供的RedisCacheManager,RedisCache。

如果我们要使用Caffeine框架。只需要引入Caffeine,Spring Cache就会默认使用CaffeineCacheManager和CaffeineCache。

// https://blog.csdn.net/hacker_lli/article/details/108632749 

package org.example.cache.caffeine;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 方案一(常用):定制化缓存Cache
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .initialCapacity(100)
                .maximumSize(10_000))
        // 如果缓存种没有对应的value,通过createExpensiveGraph方法同步加载  buildAsync是异步加载
        //.build(key -> createExpensiveGraph(key))
        ;


        // 方案二:传入一个CaffeineSpec定制缓存,它的好处是可以把配置方便写在配置文件里
        //cacheManager.setCaffeineSpec(CaffeineSpec.parse("initialCapacity=50,maximumSize=500,expireAfterWrite=5s"));
        return cacheManager;
    }

}

常用注解

Spring Cache有几个常用注解,分别为@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig。除了最后一个CacheConfig外,其余四个都可以用在类上或者方法级别上,如果用在类上,就是对该类的所有public方法生效,下面分别介绍一下这几个注解。

cacheable

@Cacheble注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上。

  • value、cacheNames:两个等同的参数(cacheNamesSpring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
  • key:和cacheNames共同组成一个key,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
  • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试,**在函数调用前进行判断,因此result这种spel里面进行判断时,永远为null.**
  • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断
  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
  • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
  • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。
作用和配置方法

img

image-20240828144849676

参考链接

/** * 根据ID获取Tasklog * @param id * @return */
 @Cacheable(value = CACHE_KEY, key = "#id",condition = "#result != null")
 public Tasklog findById(String id){
     System.out.println("FINDBYID");
     System.out.println("ID:"+id);
     return taskLogMapper.selectById(id);
 }
缓存中spel表达式可取值

img

@CachePut

@CachePut 的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用

使用 @CachePut 注解,可以更新缓存的内容,而不会影响方法的执行。也就是说,方法始终会被执行并将结果缓存起来

@Cacheable@CachePut 的区别在于,@Cacheable 会跳过运行方法,而 @CachePut 会实际运行方法,然后将结果放入缓存。

作用和配置方法

img

/** * 添加tasklog * @param tasklog * @return */
@CachePut(value = CACHE_KEY, key = "#tasklog.id")
public Tasklog create(Tasklog tasklog){
    System.out.println("CREATE");
    System.err.println (tasklog);
    taskLogMapper.insert(tasklog);
    return tasklog;
}

@CacheEvict

一般用在更新或者删除的方法上

缓存的数据如果不进行清理,会保留大量陈旧或未使用甚至是过期的数据。

可以使用 @CacheEvict 注解来表示删除一个、多个或所有的值,以刷新缓存:

作用和配置方法

img

/** * 根据ID删除Tasklog * @param id */
@CacheEvict(value = CACHE_KEY, key = "#id")
public void delete(String id){
    System.out.println("DELETE");
    System.out.println("ID:"+id);
    taskLogMapper.deleteById(id);
}
//使用参数 allEntries 与要清空的缓存结合使用;这将清除缓存 addresses 中的所有条目。
@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}

@Caching

Java注解的机制决定了,一个方法上只能有一个相同的注解生效。那有时候可能一个方法会操作多个缓存(这个在删除缓存操作中比较常见,在添加操作中不太常见)。

//@Caching注解就是用来解决这类情况的,大家一看它的源码就明白了
public @interface Caching {
	Cacheable[] cacheable() default {};
	CachePut[] put() default {};
	CacheEvict[] evict() default {};
}
//有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id–>user;username—>user;email—>user的缓存;此时就需要@Caching组合多个注解标签了
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) {
}

@CacheConfig

前面提到的四个注解,都是Spring Cache常用的注解。每个注解都有很多可以配置的属性。

但这几个注解通常都是作用在方法上的,而有些配置可能又是一个类通用的,这种情况就可以使用@CacheConfig了,它是一个类级别的注解,可以在类级别上配置cacheNames、keyGenerator、cacheManager、cacheResolver等。

例如:

所有的@Cacheable()里面都有一个value=“xxx”的属性,这显然如果方法多了,写起来也是挺累的,如果可以一次性声明完 那就省事了, 所以,有了@CacheConfig这个配置,@CacheConfig is a class-level annotation that allows to share the cache names,如果你在你的方法写别的名字,那么依然以方法的名字为准。

/** * 测试服务层 */
@Service
@CacheConfig(cacheNames= "taskLog")
public class TaskLogService {
 
    @Autowired  private TaskLogMapper taskLogMapper;
    @Autowired  private net.sf.ehcache.CacheManager cacheManager;
 
    /** * 缓存的key */
    public static final String CACHE_KEY   = "taskLog";
 
    /** * 添加tasklog * @param tasklog * @return */
    @CachePut(key = "#tasklog.id")
    public Tasklog create(Tasklog tasklog){
        System.out.println("CREATE");
        System.err.println (tasklog);
        taskLogMapper.insert(tasklog);
        return tasklog;
    }
 
    /** * 根据ID获取Tasklog * @param id * @return */
    @Cacheable(key = "#id")
    public Tasklog findById(String id){
        System.out.println("FINDBYID");
        System.out.println("ID:"+id);
        return taskLogMapper.selectById(id);
    }
}

自定义缓存注解

比如之前的那个@Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如

@Caching(put = {
    @CachePut(value = "user", key = "#user.id"),
    @CachePut(value = "user", key = "#user.username"),
    @CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}
@UserSaveCache
public User save(User user){}

基于 XML 的声明式缓存

如果无法访问应用的源码,或者想从外部注入缓存行为,也可以使用基于 XML 的声明式缓存。

spring中文网

<!-- 希望缓存的服务 -->
<bean id="customerDataService" 
  class="com.your.app.namespace.service.CustomerDataService"/>

<bean id="cacheManager" 
  class="org.springframework.cache.support.SimpleCacheManager"> 
    <property name="caches"> 
        <set> 
            <bean 
              class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
              name="directory"/> 
            <bean 
              class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
              name="addresses"/> 
        </set> 
    </property> 
</bean>
<!-- 定义缓存行为 -->
<cache:advice id="cachingBehavior" cache-manager="cacheManager">
    <cache:caching cache="addresses">
        <cache:cacheable method="getAddress" key="#customer.name"/>
    </cache:caching>
</cache:advice>

<!-- 将该行为应用于 CustomerDataService 接口的所有实现 -->
<aop:config>
    <aop:advisor advice-ref="cachingBehavior"
      pointcut="execution(* com.your.app.namespace.service.CustomerDataService.*(..))"/>
</aop:config>

使用缓存带来的问题

双写不一致

使用缓存会带来许多问题,尤其是高并发下,包括缓存穿透、缓存击穿、缓存雪崩、双写不一致等问题。

其中主要聊一下双写不一致的问题,这是一个比较常见的问题,其中一个常用的解决方案是,更新的时候,先删除缓存,再更新数据库。所以Spring Cache的@CacheEvict会有一个beforeInvocation的配置。

但使用缓存通常会存在缓存中的数据和数据库中不一致的问题,尤其是调用第三方接口,你不会知道它什么时候更新了数据。但使用缓存的业务场景很多时候并不需求数据的强一致,比如首页的热点文章,我们可以让缓存一分钟失效,这样就算一分钟内,不是最新的热点排行也没关系。


占用额外的内存

这个是无可避免的。因为总要有一个地方去放缓存。不管是ConcurrentHashMap也好,Redis也好,Caffeine也好,总归是会占用额外的内存资源去放缓存的。但缓存的思想正是用空间去换时间,有时候占用这点额外的空间对于时间上的优化来说,是非常值得的。

这里需要注意的是,SpringCache默认使用的是ConcurrentHashMap,它不会自动回收key,所以如果使用默认的这个缓存,程序就会越来越大,并且得不到回收。最终可能导致OOM。

springcache +redis

maven 依赖

<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
         <exclusions>
             <!--排除lettuce客户端(默认使用lettuce客户端)-->
             <exclusion>
                 <artifactId>lettuce-core</artifactId>
                 <groupId>io.lettuce</groupId>
             </exclusion>
         </exclusions>
     </dependency>
     <dependency>
         <groupId>redis.clients</groupId>
         <artifactId>jedis</artifactId>
         <version>3.7.1</version>
     </dependency>
spring:
  config:
    activate:
      on-profile:
        - redisdev
  cache:
    type: REDIS   #设置缓存组件类型
    time-to-live: 3600000   #设置缓存过期时间
    cache-names: redisUser
    #指定默认前缀,如果此处我们指定了前缀则使用我们指定的前缀,推荐此处不指定前缀
    #spring.cache.redis.key-prefix=CACHE_
    #是否开始前缀,建议开启
    use-key-prefix: true
    cache-null-values: true #是否缓存空值,防止缓存穿透
  redis:
    database: 1
    host: 127.0.0.1
    port: 6379
    password:
package org.example.cache.redis集成;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author: noob
 * @description :
 * @Date : 13:38 2024/8/28
 */
@Configuration
public class JedisConfig {

    @Value("${spring.redis.host}")
    String redisHost;
    @Value("${spring.redis.password}")
    String password;

    @Value("${spring.redis.port}")
    int port;
    @Value("${spring.redis.database}")
    int database;


    @Bean
    public RedisStandaloneConfiguration standaloneConfig() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisHost);
        configuration.setPort(port);
        configuration.setDatabase(database);
        return configuration;
    }

    @Bean
    public JedisPoolConfig poolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMinIdle(300);
        poolConfig.setMaxIdle(500);
        poolConfig.setMaxTotal(5000);
        poolConfig.setMaxWaitMillis(1000);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(true);
        poolConfig.setTestWhileIdle(true);
        return poolConfig;
    }

    @Bean
    public JedisConnectionFactory connectionFactory(RedisStandaloneConfiguration standaloneConfig) {
        JedisConnectionFactory factory = new JedisConnectionFactory(standaloneConfig);
        // 添加redis连接池
        factory.setPoolConfig(poolConfig());
        factory.setUsePool(true);
        return factory;
    }


//  jedis  一种简单的配置方式
//         public JedisPoolConfig jedisPoolConfig() {
//         JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//         return jedisPoolConfig;
//     }
//
//     @Bean
//     JedisConnectionFactory jedisConnectionFactory() {
//         JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
//         jedisConnectionFactory.setPoolConfig(jedisPoolConfig());
//         jedisConnectionFactory.setHostName(redisHost);
//         jedisConnectionFactory.setPassword(password);
//         jedisConnectionFactory.setPort(port);
//         jedisConnectionFactory.setDatabase(database);
//         return jedisConnectionFactory;
//     }


}
package org.example.cache.redis集成;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import javax.annotation.Resource;

import static org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig;

/**
 * @author: noob
 * @description :  https://blog.csdn.net/echizao1839/article/details/102660649
 * <p>
 * 正常继承 support接口进行继承
 * @Date : 14:09 2024/8/28
 */
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {

    /**
     * 自定义缓存的redis的KeyGenerator【key生成策略】
     * 注意: 该方法只是声明了key的生成策略,需在@Cacheable注解中通过keyGenerator属性指定具体的key生成策略
     * 可以根据业务情况,配置多个生成策略
     * 如: @Cacheable(value = "key", keyGenerator = "cacheKeyGenerator")
     */
    @Override
    public KeyGenerator keyGenerator() {
        /**
         * target: 类
         * method: 方法
         * params: 方法参数
         */
        return (target, method, params) -> {
            //获取代理对象的最终目标对象
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName()).append(":");
            sb.append(method.getName()).append(":");
            //调用SimpleKey的key生成器
            Object key = SimpleKeyGenerator.generateKey(params);
            return sb.append(key);
        };
    }


    /**
     * 自定义生成redis-key
     */
    // @Override
    // public KeyGenerator keyGenerator() {
    //     return (o, method, objects) -> {
    //         StringBuilder sb = new StringBuilder();
    //         sb.append(o.getClass().getName()).append(".");
    //         sb.append(method.getName()).append(".");
    //         for (Object obj : objects) {
    //             sb.append(obj.toString());
    //         }
    //         return sb.toString();
    //     };
    // }


    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }

    @Override
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }


    @Resource
    private RedisConnectionFactory factory;

    @Override
    public CacheManager cacheManager() {
        RedisCacheConfiguration cacheConfiguration =
                defaultCacheConfig()
                        .disableCachingNullValues()
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
    }


}
package org.example.cache.redis集成;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;


public interface RedisService  {


    public List<User> list()  ;


    public void del(Integer id) ;

    public User select(Integer id) ;
}


package org.example.cache.redis集成;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Service
@CacheConfig(cacheNames = "user",keyGenerator = "keyGenerator")
public class RedisServiceImpl implements RedisService {

    @Cacheable(value = "user", key = "'list'")
    @Override
    public List<User> list() {
        System.out.println("=========list");
        User user1 = new User();
        user1.setId(1);
        user1.setName("老大");
        User user2 = new User();
        user2.setId(2);
        user2.setName("老二");
        List<User> users = new ArrayList<>();
        users.add(user1);
        users.add(user2);
        return users;
    }

    @CacheEvict(value = "user", key = "'list'")
    @Override
    public void del(Integer id) {
        System.out.println("************************************+id");
        List<User> users = new ArrayList<>();
        Iterator<User> iterator = users.iterator();
        while (iterator.hasNext()) {
            User user = iterator.next();
            if (user.getId().equals(id)) {
                iterator.remove();
                break;
            }
        }
    }

    @CachePut(value = "demo", key = "#result==null")
    @Override
    public User select(Integer id) {
        System.out.println("===============dddd================");
        if (id == 0) {
            return null;
        }
        User user = new User();
        user.setId(100);
        user.setName("测试");
        return user;
    }
}

package org.example.cache.redis集成;


import org.example.cache.注解使用.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * https://blog.csdn.net/lingerlan510/article/details/121906813  集成redisTemplate
 * redis 注解集成
 * @author: noob
 * @description :
 * @Date : 9:05 2024/8/28
 */
@RestController
@RequestMapping("/redis")
public class TestRedisController {

    @Resource
    private RedisService redisService;
    @Autowired
    private CacheManager cacheManager;

    @RequestMapping("/get")
    public void test1() {
        Object obj = redisService.list();
        System.out.println("----------验证缓存是否生效----------");
        Cache cache = cacheManager.getCache("user");
        Cache cache2 = cacheManager.getCache("redisUser");
        System.out.println(cache);
        System.out.println(cache2);
        System.out.println(cache.get(1));
        System.out.println(cache2.get(1));
    }


}

package org.example.cache.redis集成;

/**
 * @author: noob
 * @description :
 * @Date : 11:16 2024/8/28
 */

public class User {

    private Integer id;

    private String name;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

springcache +caffenie

如前文所示

后记:

redisTemplate、jedis、lettuce、redission的对比

2.SpringCache的使用注意事项
@CacheEvict注解中的allEntries = true属性会将当前片区中的所有缓存数据全部清除,请谨慎使用
@CacheEvict注解适用用于失效模式,也即更新完数据库数据后删除缓存数据
@CachePut注解用于适用于双写模式,更新完数据库后写入到缓存中
SpringCache不是只能和Redis中间件进行整和,和其他缓存中间件也可以整合实现缓存管理
Redis的作用也不仅仅是用作缓存,也可以用于功能实现,实现分布式锁,注意区分redis分布式锁和SpringCache
配置文件中spring.cache.redis.key-prefix的配置一般不进行设置
配置文件中spring.cache.redis.cache-null-values=true一般需要设置(null值缓存),可以有效的防止缓存穿透

3.SpringCache的不足
SpringCache只对读模式下的缓存失效进行了处理,对于写模式下的缓存失效没有相应的处理,需要我们自己采取其他方式来处理。
缓存中常见的失效场景及解决方案:

缓存穿透:查询一个null数据 解决方案:缓存空数据
缓存击穿:大量并发同时查询一个刚好过期的数据,解决方案:加锁
缓存雪崩:大量的key同时过期,解决方案:所有key都添加上随机的过期时间
读模式下的缓存失效处理方案:

缓存穿透:cache-null-values: true,允许写入空值
缓存击穿:@Cacheable(sync = true),加锁
缓存雪崩:time-to-live:xxx,设置不同的过期时间
提示:
1、对于常规数据(读多写少,及时性、一致性要求不高的数据)完全可以使用 Spring Cache
2、对于特殊数据(比如要求高一致性)则需要特殊处理
原文链接:https://blog.csdn.net/lingerlan510/article/details/121906813