spring boot中的cache使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wujiaqi0921/article/details/79123873

1.缓存介绍

1.1缓存简介

缓存:缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。为什么使用缓存?究其原因就是缓存的读写速度远快与磁盘,从减轻I/O开销和加快运行速度方便都有很好的效果。那么我们缓存什么?哪些经常读取而又不经常修改的数据,那些数据量较大又很少修改的数据。缓存策略三要素:缓存命中率、缓存更新策略、最大缓存容量。

1.2缓存命中率

如何解读缓存命中率,就是一个被缓存的数据被读取的次数/总读取次数。而这个命中率也是越高越好。
命中率=从缓存中读取的次数/总读取次数
未命中率=没有从换存中读取的次数/总读取次数
衡量一个缓存方案的好坏标准是:缓存命中率。缓存命中率越高,缓存方法设计的越好。

1.3缓存更新策略

说起缓存,那也就必定会聊到缓存更新策略,即在缓存到达最大容量时改如何处理。常用的缓存策略有:

  1. FIFO(First In First Out) 队列,先进先出,典型应用:MySQL的 query cache,采用的就是这种简单的缓存更新策略
  2. LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除,借助计数器实现;
  3. LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除,借助计数器和队列实现;
  4. TTL(Time To Live ):存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)
  5. TTI(Time To Idle):空闲期,即一个数据多久没被访问将从缓存中移除的时间。

1.4最大缓存容量

大容量的缓存虽然可以在硬盘进行读写工作状态下,让更多的数据存储在缓存中,以提高硬盘的访问速度,但并不意味着缓存越大就越出众。缓存的应用存在一个算法的问题,即便缓存容量很大,而没有一个高效率的算法,那将导致应用中缓存数据的命中率偏低,无法有效发挥出大容量缓存的优势。算法是和缓存容量相辅相成,大容量的缓存需要更为有效率的算法,否则性能会大大折扣,从技术角度上说,高容量缓存的算法是直接影响到硬盘性能发挥的重要因素。更大容量缓存是未来硬盘发展的必然趋势。

以上便是缓存的基本知识,接下来我们来了解一下spring提供的cache。本文也主要以注解方式实现。

2.示例

2.1添加maven依赖

首先我们先添加maven依赖

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

2.2源码简单分析

接着我们来看下spring提供的核心cache接口:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cache;

import java.util.concurrent.Callable;

public interface Cache {
    String getName();

    Object getNativeCache();

    Cache.ValueWrapper get(Object var1);

    <T> T get(Object var1, Class<T> var2);

    <T> T get(Object var1, Callable<T> var2);

    void put(Object var1, Object var2);

    Cache.ValueWrapper putIfAbsent(Object var1, Object var2);

    void evict(Object var1);

    void clear();

    public static class ValueRetrievalException extends RuntimeException {
        private final Object key;

        public ValueRetrievalException(Object key, Callable<?> loader, Throwable ex) {
            super(String.format("Value for key \'%s\' could not be loaded using \'%s\'", new Object[]{key, loader}), ex);
            this.key = key;
        }

        public Object getKey() {
            return this.key;
        }
    }

    public interface ValueWrapper {
        Object get();
    }
}

spring提供的context-support包中默认给我们提供了四中实现:Caffeine、GuavaCache、EhCacheCache、JCacheCache。另外,因为我们在应用中并不是使用一个Cache,而是多个,因此Spring还提供了CacheManager抽象,用于缓存的管理:

package org.springframework.cache;  
import java.util.Collection;  
public interface CacheManager {  
    Cache getCache(String name); //根据Cache名字获取Cache   
    Collection<String> getCacheNames(); //得到所有Cache的名字  
}  

同样每一种缓存提供了默认的实现:CaffeineCacheManager、EhCacheCacheManager、GuaCacheManager、JCacheManager.
当我们调用cacheManager.getCache(cacheName) 时,会先从第一个cacheManager中查找有没有cacheName的cache,如果没有接着查找第二个,如果最后找不到,因为fallbackToNoOpCache=true,那么将返回一个NOP的Cache否则返回null。

除了GuavaCacheManager之外,其他Cache都支持Spring事务的,即如果事务回滚了,Cache的数据也会移除掉。
Spring不进行Cache的缓存策略的维护,这些都是由底层Cache自己实现,Spring只是提供了一个Wrapper,提供一套对外一致的API。

另外还提供了CompositeCacheManager来综合管理CacheManager,从下面源码中可以看到,在获取缓存时会轮询从不同的cacheManage查询直到查询到一个结果或者返回NULL。

public class CompositeCacheManager implements CacheManager, InitializingBean {
    private final List<CacheManager> cacheManagers = new ArrayList();
    private boolean fallbackToNoOpCache = false;

    public CompositeCacheManager() {
    }

    public CompositeCacheManager(CacheManager... cacheManagers) {
        this.setCacheManagers(Arrays.asList(cacheManagers));
    }

    public void setCacheManagers(Collection<CacheManager> cacheManagers) {
        this.cacheManagers.addAll(cacheManagers);
    }

    public void setFallbackToNoOpCache(boolean fallbackToNoOpCache) {
        this.fallbackToNoOpCache = fallbackToNoOpCache;
    }

    public void afterPropertiesSet() {
        if(this.fallbackToNoOpCache) {
            this.cacheManagers.add(new NoOpCacheManager());
        }

    }

    public Cache getCache(String name) {
        Iterator var2 = this.cacheManagers.iterator();

        Cache cache;
        do {
            if(!var2.hasNext()) {
                return null;
            }

            CacheManager cacheManager = (CacheManager)var2.next();
            cache = cacheManager.getCache(name);
        } while(cache == null);

        return cache;
    }

    public Collection<String> getCacheNames() {
        LinkedHashSet names = new LinkedHashSet();
        Iterator var2 = this.cacheManagers.iterator();

        while(var2.hasNext()) {
            CacheManager manager = (CacheManager)var2.next();
            names.addAll(manager.getCacheNames());
        }

        return Collections.unmodifiableSet(names);
    }
}

3 注解讲解

接下来介绍一下我们要使用的注解:

1.@Cacheable

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。
value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个。如@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。如@Cacheable(value=”testcache”,key=”#userName”)
condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。如@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
注:除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
@Cacheable(cacheResolver=”MyCacheResolver”)执行缓存解析器
@Cacheable(cacheManager=”“)执行缓存管理器,默认使用currentMapCacheManager
@Cacheable(cacheNames=”“)指定缓存的名称
@Cacheable(keyGenerator=”“)在未指定key时指定缓存key值的生成器
@Cacheable(unless=”“) 过滤不需要缓存的值,支持spEl表达式。

属性名称 位置 描述 示例
methodName root对象 当前方法名 root.methodName
method root对象 当前方法 root.method.name
target root对象 当前被调用的对象 root.target
targetClass root对象 当前被调用的对象的class root.targetClass
args root对象 当前方法参数组成的数组 root.args[0]
caches root对象 当前被调用的方法使用的Cache root.caches[0].name
argument name 执行上下文 当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数 user.id
result 执行上下文 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache evict’的beforeInvocation=false) result
    @Cacheable(value = "user", key = "#id", condition = "#id != '123'", cacheResolver = "", cacheManager = "", cacheNames = "",keyGenerator = "", unless = "")
    public User findById(String id) {
        System.out.println("执行数据库查询方法");
        return userDao.findById(id);
    }

2.@CachePut

在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。

    @CachePut(value = "user", key = "#id", condition = "#id != '321'")
    public User findById(String id) {
        System.out.println("执行数据库查询方法");
        return userDao.findById(id);
    }

3.@CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。
allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。如:@CachEvict(value=”testcache”,beforeInvocation=true)
allEntries指定为true时,则会清楚所有缓存。
其他参数和@Cacheable相同

    @CacheEvict(value = "user", key = "#id")
    public void deleteUserById(String id) {

    }

4.@Caching

@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。使用如下

    @Caching(cacheable = {@Cacheable(value = "user", key = "#id", condition = "#id != '123'"),
            @Cacheable(value = "user", key = "#id", condition = "#id != '321'")}
    )
    public User findById(String id) {
        System.out.println("执行数据库查询方法");
        return userDao.findById(id);
    }

在上面我们没有没使用指定的缓存解析器,以及key生成器。那么我们如何来实现自己的解析器,缓存管理类或者key生成器呢?我们可以通过实现org.springframework.cache.interceptor.KeyGenerator接口实现自己想要的主键生成器,通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器。在这里就不进行代码展示了。
这里还需要提到@CacheConfig用于指定类全局配置。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页