Caffeine缓存和@Cacheable注解

本文介绍了Caffeine作为高性能的Java缓存库,其基础使用包括自动加载策略(同步、异步)、其他API(如统计信息、CacheWriter、移除监听)和缓存淘汰机制(LRU、LFU)。此外,还简述了Spring中的@Cacheable注解在缓存管理中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Caffeine的基础使用

借鉴了https://www.jianshu.com/p/9ee291147617

1.1 简介

Caffeine是基于Java 8的高性能,接近最佳的缓存工具库。Caffeine使用Google Guava启发的API提供内存缓存。所以它的使用成本较低,跟Guava的API大致一致。

它主要有以下几个功能:

  • 自动将条目自动加载到缓存中,可以选择同步或异步加载

  • 基于频率和新近度超过最大值时基于大小的逐出

  • 自上次访问或上次写入以来测得的基于时间的条目到期

  • 发生第一个陈旧的条目请求时,异步刷新

  • 键自动包装在弱引用中

  • 值自动包装在弱引用或软引用中

  • 逐出(或以其他方式删除)条目的通知

  • 写入通知

  • 缓存访问统计信息的

1.2 常见API

Cache分为LoadingCache(同步缓存),AsyncLoadingCache(异步缓存)

  • pom依赖
      <dependency>
            <groupId>com.github.benmanes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.8.5</version>
        </dependency>
1.2.1 人工加载策略
Cache<Object, Object> cache = Caffeine.newBuilder()
        .expireAfterWrite(1, TimeUnit.SECONDS)
            .expireAfterAccess(1, TimeUnit.SECONDS)
        .maximumSize(10)//最大条数
        .build();//定义cache
User user1=(User) cache.get(id, v-> userDao.getOne(id));//如果cache不存在,查询数据库
1.2.2 自动加载同步
LoadingCache<String, User> userDaoCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS)
            .expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(10).build(key -> userDao.getOne(key))
User user = UserDaoCache.get("1");
1.2.3 自动加载异步
  AsyncLoadingCache<String, User> asynUserDaoCache = Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.SECONDS).expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(10)
            .buildAsync(key -> userDao.getOne(key));
  User user = asynUserDaoCache.get("1").get();
1.3. 其他API
1.3.1 统计缓存信息
Cache<Object, Object> cacheStats = Caffeine.newBuilder()
            .expireAfterWrite(1000, TimeUnit.SECONDS)
            .expireAfterAccess(1000, TimeUnit.SECONDS)
             .recordStats()//记录统计信息
            .weakKeys()//key弱引用
            .weakValues()//value 弱引用
            .maximumSize(10).build()
CacheStats stats = cacheStats.stats();//获取统计信息
1.3.2 CacheWriter
    LoadingCache<String, User> cacheWriter = Caffeine.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS)
            .writer(new CacheWriter<String, User>() {
                @Override
                public void write(String id, User user) {
                    System.out.println("***写入***" + id);//当缓存数据时,调用此方案
                }

                @Override
                public void delete(String id, User user, RemovalCause cause) {
                    System.out.println("***delete***" + id);//当手动删除数据时,调用此方法
                }
            }).build(id -> userDao.getOne(id));
1.3.3 RemovealListener监听(手动删除)

1.4. 缓存淘汰机制

常见缓存机制有:LRU(Least Recently Used)最近最少使用
LFU(Least Frequently Used)最不经常使用

1.4.1 LRU

如果一个数据在最近一段时间没有被访问到,那么认为在将来他被访问的可能性也很低,也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰
缺点是:注重对数据的使用时间是不是最近,缓存数目超过容量,缓存数据很容易淘汰

1.4.2 LFU

如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰
缺点:注重对元素的使用频次上是不是较大。
1.在短时间内如果某些元素访问频率很高,会导致后续很难淘汰,造成无效数据缓存。
2.突发流量的时候,可能由于没有及时达到足够的频率数据来保证自己驻留在缓存中,从而导致缓存的命中率下降

1.5 参数总结
maximumSize:设置缓存最大条目数,超过条目则触发回收 
maximumWeight:设置缓存最大权重,设置权重是通过weigher方法, 需要注意的是权重也是限制缓存大小的参数,并不会影响缓存淘汰策略,也不能和maximumSize方法一起使用。 
weakKeys:将key设置为弱引用,在GC时可以直接淘汰
weakValues:将value设置为弱引用,在GC时可以直接淘汰
softValues:将value设置为软引用,在内存溢出前可以直接淘汰
expireAfterWrite:写入后隔段时间过期
expireAfterAccess:访问后隔断时间过期
refreshAfterWrite:写入后隔断时间刷新
removalListener:缓存淘汰监听器,配置监听器后,每个条目淘汰时都会调用该监听器
writer:writer监听器其实提供了两个监听,一个是缓存写入或更新是的write,一个是缓存淘汰时的delete,每个条目淘汰时都会调用该监听器

2. @Cacheable注解

借鉴了https://blog.youkuaiyun.com/qq_35981283/article/details/82429603
cache方面的注解主要有一下5个

  • @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
  • @CacheEvict 触发缓存的eviction(用于删除的方法上)
  • @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
  • @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
  • @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
    项目中主要使用的是@Cacheable注解
    源码
public @interface Cacheable {

    /**
     * 设定要使用的cache的名字,必须提前定义好缓存
     */
    @AliasFor("cacheNames")
    String[] value() default {};

    /**
     * 同value(),决定要使用那个/些缓存
     */
    @AliasFor("value")
    String[] cacheNames() default {};

    /**
     * 使用SpEL表达式来设定缓存的key,如果不设置默认方法上所有参数都会作为key的一部分
     */
    String key() default "";

    /**
     * 用来生成key,与key()不可以共用
     */
    String keyGenerator() default "";

    /**
     * 设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字
     */
    String cacheManager() default "";

    /**
     * 使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用
     */
    String cacheResolver() default "";

    /**
     * 使用SpEL表达式设定出发缓存的条件,在方法执行前生效
     */
    String condition() default "";

    /**
     * 使用SpEL设置出发缓存的条件,这里是方法执行完生效,所以条件中可以有方法执行后的value
     */
    String unless() default "";

    /**
     * 用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法
     * 则只允许一个线程通过去执行方法
     */
    boolean sync() default false;

}

项目中的使用
在这里插入图片描述
在这里插入图片描述

### 清除使用 `@Cacheable` 注解配置的多级缓存 在 Spring 中,当涉及到清除由 `@Cacheable` 配置的多级缓存时,可以利用 `@CacheEvict` 或者编程方式实现。对于多级缓存而言,通常会涉及多个缓存层,在这些层次结构中的每一层都可能保存有相同键的数据副本。 #### 使用 `@CacheEvict` 为了确保所有级别的缓存都能被清空,可以在更新操作上应用 `@CacheEvict` 注解,并通过设置 `allEntries=true` 来指示要移除整个命名空间内的条目: ```java @CacheEvict(value = {"levelOneCache", "levelTwoCache"}, allEntries = true) public void clearAllCaches() { // 方法体可为空或执行其他逻辑 } ``` 此方法会在调用时触发对指定名称列表中每一个缓存区域的所有条目的清理工作[^2]。 #### 编程方式清除缓存 除了注解驱动的方式外,还可以借助于 `CacheManager` 接口提供的 API 手动控制缓存的行为。下面是一个例子展示了如何获取到具体的 `Cache` 实例并对其进行清理: ```java @Autowired private CacheManager cacheManager; public void evictMultiLevelCaches(){ Collection<String> cacheNames = Arrays.asList("levelOneCache","levelTwoCache"); for (String name : cacheNames){ Cache cache = cacheManager.getCache(name); if(cache != null){ cache.clear(); } } } ``` 这种方法允许更灵活的操作,比如只针对特定条件下的某些记录进行删除而不是全部清除[^3]。 #### 结合 Layering Cache 框架 如果项目已经集成了像 Layering Cache 这样的分层缓存解决方案,则应该查阅其官方文档以获得最佳实践指导。因为不同框架可能会有不同的机制去处理跨级别的一致性失效策略[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值