spring cache

简介

缓存有诸多好处,但网上一搜就发现缓存框架太多了,各有各的优势,比如redis,Memcached,guava

如果我们的程序想要使用缓存,就要与这些框架耦合,.在合适的时候,在代码中调用与缓存有关的接口方法,在合适的时候插入到数据库里,在合适的时候从缓存中读取数据

想一想AOP的场景,这不就是AOP该去做的吗

springCache就是这一个框架,利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层使用了什么框架,只需要简单加一个注解,就能实现缓存功能了.而且spring cache也提供了很多默认的配置,用户可以三秒钟就使用上一个很不错的缓存功能

步骤

1 加依赖

gradle:

implementation 'org.springframework.boot:spring-boot-starter-cache'

maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2 开启缓存

在启动类加上@EnableCaching注解即可开启使用缓存。

@SpringBootApplication
@EnableCaching
public class CachingApplication {

    public static void main(String[] args) {
        SpringApplication.run(CachingApplication.class, args);
    }
}

3 加缓存注解

在要缓存的方法上面添加@Cacheable注解,即可缓存这个方法的返回值。

在要缓存的方法上面添加@Cacheable注解,即可缓存这个方法的返回值。

@Override
@Cacheable("books")
public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
}

// Don't do this at home
private void simulateSlowService() {
    try {
        long time = 3000L;
        Thread.sleep(time);
    } catch (InterruptedException e) {
        throw new IllegalStateException(e);
    }
}

测试

@Override
public void run(String... args) {
    logger.info(".... Fetching books");
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
}

测试一下,可以发现。第一次和第二次(第二次参数和第一次不同)调用getByIsbn方法,会等待3秒,而后面四个调用,都会立即返回。

常用注解

SpringCache有几个常用注解分别为@Cacheable,@CachePut,@CacheEvict,@Caching,@CacheConfig

除了最后一个CacheConfig外,其余四个都可以用在类上或者方法级别上,如果用在类上,就是对盖类的所有public方法生效

@Cacheable

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

@CachePut

加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其他地方使用.它通常用在新增方法上

@CacheEvict

使用了CacheEvict注解的方法,会清空指定缓存.一般用在更新或者删除的方法上

@Caching

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

@caching就是用来解决这类情况的,大家一看它的源码就明白了

public @interface Caching {
    Cacheable[] cacheable() default {};
    CachePut[] put() default {};
    CacheEvict[] evict() default {};
}

@CacheConfig

前面提到的四个注解,都是Spring Cache常用的注解。每个注解都有很多可以配置的属性,这个我们在下一节再详细解释。但这几个注解通常都是作用在方法上的,而有些配置可能又是一个类通用的,这种情况就可以使用@CacheConfig了,它是一个类级别的注解,可以在类级别上配置cacheNames、keyGenerator、cacheManager、cacheResolver等。

使用其他缓存框架

默认数据是存储在concurrentHashMap中的如果要使用其他的缓存框架该怎么做呢

redis

1). 导入Spring Cache和Redis相关maven坐标

2). 在application.yml中配置缓存数据的过期时间

3). 在启动类上加入@EnableCaching注解,开启缓存注解功能

4). 在相应的查询(读)方法上加入@Cacheable注解

5). 在对数据库内容有改变(写)的方法上加入CacheEvict注解

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

备注: spring-boot-starter-data-redis 这个依赖前面已经引入了, 无需再次引入。

application.yml中设置缓存过期时间

spring:  
  cache:
    redis:
      time-to-live: 1800000 #设置缓存数据的过期时间

启动类上加入@EnableCaching注解

缓存带来的问题

双写不一致

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

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

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

缓存击穿

缓存击穿是指当缓存中某个热点数据过期了,在该热点数据重新载入缓存之前,有大量的查询请求穿过缓存,直接查询数据库。这种情况会导致数据库压力瞬间骤增,造成大量请求阻塞,甚至直接挂掉。

解决缓存击穿的方法也有两种,第一种是设置key永不过期;第二种是使用分布式锁,保证同一时刻只能有一个查询请求重新加载热点数据到缓存中,这样,其他的线程只需等待该线程运行完毕,即可重新从Redis中获取数据。

第一种方式比较简单,在设置热点key的时候,不给key设置过期时间即可。不过还有另外一种方式也可以达到key不过期的目的,就是正常给key设置过期时间,不过在后台同时启一个定时任务去定时地更新这个缓存。

额外占用内存

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

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

Last modification:January 29, 2023
如果觉得我的文章对你有用,请随意赞赏