Spring Boot使用Caffeine实现缓存管理 | Spring Cloud 39

一、前言

SpringCacheSpring提供的一个缓存框架,在Spring 3.1版本开始支持将缓存添加到现有的Spring应用程序中,在4.1开始,缓存已支持JSR-107注释和更多自定义的选项。

Spring Cache利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了,做到了对代码侵入性做小。

由于市面上的缓存工具实在太多,SpringCache框架还提供了CacheManager接口,可以实现降低对各种缓存框架的耦合。它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如CaffeineGuava CacheEhcacheRedis等。

结合上篇:本地缓存解决方案Caffeine | Spring Cloud 38 来实现Spring Boot使用Caffeine实现缓存。

二、SpringCache核心概念

2.1 缓存注解

  • Cache接口:缓存接口,定义缓存操作。实现有如:RedisCacheCaffeineCache

  • cacheResolver:指定缓存解析器

  • CacheManager:缓存管理器,管理各种缓存(Cache)组件;如:RedisCacheManager使用Redis作为缓存、CaffeineCacheManager使用Caffeine作为缓存

  • @Cacheable:在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用方法获取数据返回,并缓存起来。

  • @CacheEvict:将一条或多条数据从缓存中删除。

  • @CachePut:将方法的返回值放到缓存中

  • @EnableCaching:开启缓存注解功能

    扫描二维码关注公众号,回复: 15423060 查看本文章
  • @Caching:组合多个缓存注解;

  • @CacheConfig:统一配置@Cacheable中的value

2.2 缓存注解主要参数

主要针对缓存操作注解:@Cacheable@CachePut@CacheEvict

名称 解释
value 缓存的名称,在 Spring 配置文件中定义,必须指定至少一个
例如:@Cacheable(value="mycache") 或者@Cacheable(value={"mycache1","mycache2"})
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
例如:@Cacheable(value="mycache",key="#id")
condition 对入参进行判断,符合条件的缓存,不符合的不缓存,可以为空,
使用 SpEL 编写,属性范围只有#root获取参数类的SpEL表达式
不能使用返回结果的#resultcondition = "#result != null" 会导致所有对象都不进入缓存),
返回 true 或者 false,只有为 true 才进行缓存
例如:@Cacheable(value="mycache",condition="#id>2")
unless 对出参进行判断,符合条件的不缓存,不符合的缓存。当条件结果为true时,就不会缓存。
例如:@Cacheable(value="mycache",unless="#result == null")
allEntries
(@CacheEvict注解)
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
例如:@CachEvict(value="mycache",allEntries=true)
beforeInvocation
(@CacheEvict注解)
是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
例如:@CachEvict(value="mycache",beforeInvocation=true)

三、集成使用示例

3.1 集成方式汇总

  • 使用SimpleCacheManager作为缓存管理器
    • 特点:
      • 这种缓存管理器允许你在应用程序启动时通过配置多个CaffeineCache来创建多个缓存。
      • 这种方式可以让你为每个方法单独配置缓存过期时间。
    • 配置方式:
      • 代码配置
  • 使用CaffeineCacheManager作为缓存管理器
    • 特点:
      • 这种缓存管理器使用了一个全局的Caffeine配置来创建所有的缓存。
      • 这种方式不能为每个方法单独配置缓存过期时间,但是可以在程序启动时配置全局的缓存配置,这样就可以轻松地设置所有方法的缓存过期时间。
    • 配置方式:
      • 配置文件
      • 代码配置

3.2 Maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
</dependency>

3.3 SimpleCacheManager集成

3.3.1 定义缓存的过期时间

CacheNameTimeConstant

public interface CacheNameConstant {
    
    
    String CACHE_DEFAULT = "CACHE_DEFAULT";
    String CACHE_5SECS = "CACHE_5SECS";
    String CACHE_10SECS = "CACHE_10SECS";
    String CACHE_30SECS = "CACHE_30SECS";
    String USERS = "USERS";
}

3.3.2 缓存配置

CaffeineConfig

@Configuration
@EnableCaching
public class CaffeineConfig {
    
    
 
    @Bean
    public CacheManager caffeineCacheManager() {
    
    
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        List<CaffeineCache> caches = new ArrayList<>();
        caches.add(new CaffeineCache(CacheNameConstant.CACHE_5SECS,
                Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build()));
        caches.add(new CaffeineCache(CacheNameConstant.CACHE_10SECS,
                Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build()));
        caches.add(new CaffeineCache(CacheNameConstant.CACHE_30SECS,
                Caffeine.newBuilder().expireAfterWrite(300, TimeUnit.SECONDS).build()));
        caches.add(new CaffeineCache(CacheNameConstant.USERS,
                Caffeine.newBuilder().expireAfterWrite(120, TimeUnit.SECONDS).build()));
        cacheManager.setCaches(caches);
        return cacheManager;
    }
}

3.4 CaffeineCacheManager集成

3.4.1 配置文件方式

CaffeineSpec字符格式规范:https://github.com/ben-manes/caffeine/wiki/Specification-zh-CN

CaffeineSpecCaffeine提供了一个简单的字符格式配置。这里的字符串语法是一系列由逗号隔开的键值对组成,其中每个键值对对应一个配置方法。但是这里的字符配置不支持需要对象来作为参数的配置方法,比如 removalListener,这样的配置必须要在代码中进行配置。

以下是各个配置键值对字符串所对应的配置方法:

  • initialCapacity=[integer]: 相当于配置 Caffeine.initialCapacity
  • maximumSize=[long]: 相当于配置 Caffeine.maximumSize
  • maximumWeight=[long]: 相当于配置 Caffeine.maximumWeight
  • expireAfterAccess=[持续时间]: 相当于配置 Caffeine.expireAfterAccess
  • expireAfterWrite=[持续时间]: 相当于配置 Caffeine.expireAfterWrite
  • refreshAfterWrite=[持续时间]: 相当于配置 Caffeine.refreshAfterWrite
  • weakKeys: 相当于配置 Caffeine.weakKeys
  • weakValues: 相当于配置 Caffeine.weakValues
  • softValues: 相当于配置 Caffeine.softValues
  • recordStats: 相当于配置 Caffeine.recordStats

持续时间可以通过在一个integer类型之后跟上一个"d","h","m",或者"s"来分别表示天,小时,分钟或者秒。

maximumSizemaximumWeight或者将weakKeysweakValues 在一起使用是不被允许的

配置文件示例:

server:
  port: 6606
spring:
  cache:
    type: caffeine
    cache-names: USERS
    caffeine:
      spec: initialCapacity=50,maximumSize=256,expireAfterWrite=300s,refreshAfterWrite=300s,recordStats

cache-names:多个缓存器名称请用,分割。

3.4.2 代码配置方式

CaffeineConfig

@Bean
public CacheManager cacheManager() {
    
    
    Caffeine<Object, Object> caffeineCache = caffeineCache();

    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(caffeineCache);
    // 设定缓存器名称
    cacheManager.setCacheNames(getNames());
    // 值不可为空
    cacheManager.setAllowNullValues(false);
    return cacheManager;
}

private static List<String> getNames() {
    
    
    List<String> names = new ArrayList<>(1);
    names.add("USERS");
    return names;
}

@Bean
public Caffeine<Object, Object> caffeineCache() {
    
    
    return Caffeine.newBuilder()
            // 设置最后一次写入或访问后经过固定时间过期
            .expireAfterWrite(60, TimeUnit.SECONDS)
            // 初始的缓存空间大小
            .initialCapacity(100)
            // 缓存的最大条数
            .maximumSize(1000);
}

/**
 * Caused by: java.lang.IllegalStateException: refreshAfterWrite requires a LoadingCache
 *
 * @return
 */
@Bean
public CacheLoader<Object, Object> cacheLoader() {
    
    
    return this::loadData;
}

/**
 * 根据key加载缓存元素
 * @param key
 * @return
 */
private Object loadData(Object key) {
    
    
    return null;
}

3.5 外部获取缓存值

@Autowired
CacheManager  cacheManager;

Cache cache =  cacheManager.getCache("USERS");
return cache.get(key).get();

其中USERS为上文定义的缓存器名称。

四、Spring-Cache的不足

4.1 读模式

4.1.1 缓存穿透

  • 问题描述:查询结果返回是null数据。
  • 解决:缓存空对象 cache-null-values: truecacheManager.setAllowNullValues(true)

4.1.2 缓存击穿

  • 问题描述:大量并发同时查询一个过期的数据。
  • 解决:@Cacheable(sync = true) 通过加锁解决击穿。

4.1.3 缓存雪崩

  • 问题描述:缓存集中在一段时间内失效,引发大量缓存穿透,所有的查询都落在数据库上,造成缓存雪崩。
  • 解决:使用SimpleCacheManager设置多个缓存器并设置不同的过期时间,使用时按需指定不同的缓存器,让缓存失效的时间尽量分布均匀。

五、附录

5.1 SpEL上下文

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如find(User user),可以通过#user.id获得参数 #user.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如: unlesscacheEvictbeforeInvocation=false #result

表达式示例:

使用参数作为key:使用方法参数时我们可以直接使用#参数名或者#p参数Index

@Cacheable(value = "users", key = "#id")
public User find(Integer id) {
    
    
    return null;
}

@Cacheable(value = "users", key = "#p0")
public User find(Integer id) {
    
    
    return null;
}

@Cacheable(value = "users", key = "#user.id")
public User find(User user) {
    
    
    return null;
}

@Cacheable(value = "users", key = "#p0.id")
public User find(User user) {
    
    
    return null;
}

5.2 SpEL运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 `&&,
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],^[…],$[…]

猜你喜欢

转载自blog.csdn.net/ctwy291314/article/details/130366608
今日推荐