Caffeine实战教程篇

本文全面介绍了Caffeine缓存库的实战教程,包括其高级特性、淘汰机制、应用场景及常见问题解决方案,助力快速入门并避免陷阱。

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

【干货点】 该篇文章是前三篇文章Caffeine系列的总结,算是全网最全的实战教程了,每个知识点都有相关demo和应用场景,以及相关的坑,这些都一一声明了,看完该篇文章,你就能够入门Caffeine了,可以真正用进项目了。

前阵子看见这几个字,确实是颇有感触,有很多想法、很多要做的事情,不过我也明白,急不了的,那就慢慢来。

我这边专门维护了一个github仓库,后续存放Caffeine实战文章、教程分析、以及组件和应用,有兴趣关注下

image.png

github:https://github.com/wiatingpub/Caffeine

正文

如果是想直接看官网教程的请移步:https://github.com/ben-manes/caffeine/wiki

而如果还想结合实际应用场景,以及各种坑的,请看本文。

最近来了一个实习生小张,看了我在公司项目中使用的缓存框架Caffeine,三天两头跑来找我取经,说是要把Caffeine吃透,为此无奈的也只能一个个细心解答了。

后来这件事情被总监直到了,说是后面还有新人,让我将相关问题和细节汇总成一份教程,权当共享好了,该份教程也算是全网第一份,结合了目前我司游戏中业务场景的应用和思考,以及踩过的坑。

实习生小张:稀饭稀饭,以前我们游戏中应用的缓存其实是谷歌提供的ConcurrentLinkedHashMap,为什么后面你强烈要求换成用Caffeine呢?

关于上面的问题,具体有以下几个原因:

  • 使用谷歌提供的ConcurrentLinkedHashMap有个漏洞,那就是缓存的过期只会发生在缓存达到上限的情况,否则便只会一直放在缓存中。咋一看,这个机制没问题,是没问题,可是却不合理,举个例子,有玩家上线后加载了一堆的数据放在缓存中,之后便不再上线了,那么这份缓存便会一直存在,知道缓存达到上限。

  • ConcurrentLinkedHashMap没有提供基于时间淘汰时间的机制,而Caffeine有,并且有多种淘汰机制,并且支持淘汰通知。

  • 目前Spring也在推荐使用,Caffeine 因使用 Window TinyLfu 回收策略,提供了一个近乎最佳的命中率。

实习生小张:哦哦哦,我了解了,是否可以跟我介绍下Caffeine呢?

可以的,Caffeine是基于Java8的高性能缓存库,可提供接近最佳的命中率。Caffeine的底层使用了ConcurrentHashMap,支持按照一定的规则或者自定义的规则使缓存的数据过期,然后销毁。

再说一个劲爆的消息,很多人都听说过Google的GuavaCache,而没有听说过Caffeine,其实和Caffeine相比,GuavaCache简直就是个弟中弟,这不SpringFramework5.0(SpringBoot2.0)已经放弃了Google的GuavaCache,转而选择了Caffeine。

caffeine对比

为什么敢这么夸Caffeine呢?我们可以用官方给出的数据说话。

image-20201115224043394

Caffeine提供了多种灵活的构造方法,从而可以创建多种特性的本地缓存。

  1. 自动把数据加载到本地缓存中,并且可以配置异步;

  2. 基于数量剔除策略;

  3. 基于失效时间剔除策略,这个时间是从最后一次操作算起【访问或者写入】;

  4. 异步刷新;

  5. Key会被包装成Weak引用;

  6. Value会被包装成Weak或者Soft引用,从而能被GC掉,而不至于内存泄漏;

  7. 数据剔除提醒;

  8. 写入广播机制;

  9. 缓存访问可以统计;

实习生小张:我擦,这么强大,为什么可以这么强大呢,稀饭你不是自称最熟悉Caffeine的人吗?能否给我大概所说内部结构呢?

我日,我没有,我只是说在我们项目组我最熟悉,别污蔑我

img

那接下来我大概介绍下Caffeine的内部结构

image-20201121143735194
  • Cache的内部包含着一个ConcurrentHashMap,这也是存放我们所有缓存数据的地方,众所周知,ConcurrentHashMap是一个并发安全的容器,这点很重要,可以说Caffeine其实就是一个被强化过的ConcurrentHashMap。

  • Scheduler,定期清空数据的一个机制,可以不设置,如果不设置则不会主动的清空过期数据。

  • Executor,指定运行异步任务时要使用的线程池。可以不设置,如果不设置则会使用默认的线程池,也就是ForkJoinPool.commonPool()

实习生小张:听起来就是一个强化版的ConcurrentHashMap,那么需要导入什么包吗?

Caffeine的依赖,其实还是很简单的,直接引入maven依赖即可。

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

实习生小张:可以,导入成功了,你一直和我说Caffeine的数据填充机制设计的很优美,不就是put数据吗?有什么优美的?说说看吗?

是put数据,只是针对put数据,Caffeine提供了三种机制,分别是

  • 手动加载

  • 同步加载

  • 异步加载

我分别举个例子,比如手动加载

/**
 * @author xifanxiaxue
 * @date 2020/11/17 0:16
 * @desc 手动填充数据
 */
public class CaffeineManualTest {

    @Test
    public void test() {
        // 初始化缓存,设置了1分钟的写过期,100的缓存最大个数
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.MINUTES)
                .maximumSize(100)
                .build();
        int key1 = 1;
        // 使用getIfPresent方法从缓存中获取值。如果缓存中不存指定的值,则方法将返回 null:
        System.out.println(cache.getIfPresent(key1));

        // 也可以使用 get 方法获取值,该方法将一个参数为 key 的 Function 作为参数传入。如果缓存中不存在该 key
        // 则该函数将用于提供默认值,该值在计算后插入缓存中:
        System.out.println(cache.get(key1, new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return 2;
            }
        }));

        // 校验key1对应的value是否插入缓存中
        System.out.println(cache.getIfPresent(key1));

        // 手动put数据填充缓存中
        int value1 = 2;
        cache.put(key1, value1);

        // 使用getIfPresent方法从缓存中获取值。如果缓存中不存指定的值,则方法将返回 null:
        System.out.println(cache.getIfPresent(1));

        // 移除数据,让数据失效
        cache.invalidate(1);
        System.out.println(cache.getIfPresent(1));
    }
}

上面提到了两个get数据的方式,一个是getIfPercent,没数据会返回Null,而get数据的话则需要提供一个Function对象,当缓存中不存在查询的key则将该函数用于提供默认值,并且会插入缓存中。

实习生小张:如果同时有多个线程进行get,那么这个Function对象是否会被执行多次呢?

实际上不会的,可以从结构图看出,Caffeine内部最主要的数据结构就是一个ConcurrentHashMap,而get的过程最终执行的便是ConcurrentHashMap.compute,这里仅会被执行一次。

接下来说说同步加载数据

/**
 * @author xifanxiaxue
 * @date 2020/11/19 9:47
 * @desc 同步加载数据
 */
public class CaffeineLoadingTest {

    /**
     * 模拟从数据库中读取key
     *
     * @param key
     * @return
     */
    private int getInDB(int key) {
        return key + 1;
    }

    @Test
    public void test() {
        // 初始化缓存,设置了1分钟的写过期,100的缓存最大个数
        LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.MINUTES)
                .maximumSize(10
### 关于Caffeine缓存的使用教程 #### 基本概念 Caffeine 是一种高效、功能强大的本地缓存库,专为 Java 生态系统设计。它提供了多种缓存淘汰策略和灵活的配置选项,使其成为许多开发者的首选工具[^1]。 --- #### 安装与依赖引入 要开始使用 Caffeine 缓存,在 Maven 项目中需添加以下依赖项: ```xml <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.0</version> </dependency> ``` 如果是在 Spring Boot 环境下集成,则还需要额外引入 `spring-boot-starter-cache` 和相关支持包[^2]: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> ``` --- #### 配置说明 在 Spring Boot 应用程序中,可以通过 YAML 文件轻松配置 Caffeine 的行为。以下是典型的配置示例[^3]: ```yaml spring: cache: type: caffeine cache-names: myCache caffeine: spec: maximumSize=100,expireAfterWrite=10m,expireAfterAccess=10m ``` 上述配置定义了一个名为 `myCache` 的缓存实例,其最大容量为 100 条记录,并设置了写入后过期时间和访问后过期时间分别为 10 分钟。 --- #### 使用示例 ##### 示例 1:基本缓存操作 可以直接创建并管理一个简单的 Caffeine 缓存对象: ```java import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; public class CacheExample { public static void main(String[] args) { var cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(10, TimeUnit.MINUTES) .build(); cache.put("key", "value"); System.out.println(cache.getIfPresent("key")); // 输出 value } } ``` 此代码片段展示了如何构建自定义缓存实例及其基本读写方法。 --- ##### 示例 2:Spring Boot 中的声明式缓存 当结合 Spring Boot 使用时,可以利用注解简化缓存逻辑。例如,通过 `@Cacheable` 注解实现自动化的缓存加载与存储: ```java @Service public class MyService { @Cacheable(value = "myCache", cacheManager = "caffeineCacheManager") public String getData(String key) { return fetchDataFromDatabase(key); } private String fetchDataFromDatabase(String key) { // 模拟数据库查询过程 return "Data-" + key; } } ``` 在此情况下,首次调用 `getData` 方法会触发实际的数据获取流程并将结果保存到缓存中;后续相同键值的请求则直接从缓存返回数据。 --- #### 注意事项 尽管 Caffeine 提供了许多优势,但也存在一些潜在限制需要注意。例如,默认情况下它是内存中的缓存方案,因此不适合跨节点共享状态的需求[^4]。此外,合理设置缓存大小和失效策略至关重要,以免因资源耗尽引发性能问题。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值