1. Caffeine介绍

Caffeine 是用 Java 8 编写的一个 Key-Value 本地缓存库,是 Spring 默认选择的本地缓存库,缓存数据存储在内存中。与 Redis 不同的是,Redis 是分布式缓存。

Caffeine 因为使用了先进的 Window-TinyLFU 缓存淘汰算法,提供了一个近乎最佳的命中率。综合了 LRU 和 LFU 算法的长处。成为当之无愧的本地缓存之王。


2. Caffeine快速上手


2.1 导入依赖

使用前,首先导入 Caffeine 的 Maven 坐标。

<!-- 本地缓存之王Caffeine -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

由于 Caffeine 是 Spring 默认的本地缓存,因此可以不指定具体的版本,版本由 Spring Boot 管理。


2.2 基本使用


1)简单写入和查询

下面列出了 Caffeine 常用方法:增、查。

方法作用
newBuilder().build()构建 Caffeine 缓存对象
put(T k, R v)写入缓存
getIfPresent(Object key)查询缓存,如果存在则返回;如果不存在则返回 null
get(K var1, Function<? super K, ? extends V> var2)var1 为 Key ,先在缓存中查询数据,如果命中则直接返回;如果未命中则根据 var1 去数据库中查询,如果查到则存入缓存再返回。第二个入参是函数式变量,里面写数据库查询代码,可以使用 Lambda 编程

下面展示了上述方法的一段代码示例。

@SpringBootTest
public class CaffeineTest {

    @Test
    public void testSetCache() {

        // 1.创建Caffeine缓存构建器builder,Key和Value均为String类型
        Cache<String, String> cache = Caffeine.newBuilder().build();

        // 2.写入缓存
        cache.put("name", "岳飞");

        // 3.查询缓存
        String name = cache.getIfPresent("name");
        System.out.println("name = " + name);

        // 4.先去Caffeine中查询,如果未命中,则根据Key去数据库查询,并会自动将数据写入Caffeine再返回
        String hero = cache.get("hero", key -> {
            // 4.1 根据Key去数据库查询,使用了函数式编程
            // 数据库查询代码段...
            return "秦桧";
        });
        System.out.println("hero = " + hero);
    }

}

2)简单驱逐策略

由于 Caffeine 缓存数据存储在内存中,而内存相比硬盘而言空间相对有限,因此需要及时把不重要的缓存数据删除,称为【驱逐】(Evict) ,又称【缓存淘汰】。

如何衡量一个缓存数据是否【重要】?Caffeine 里采用了最先进的 Window-TinyLFU 算法进行驱逐。本章主要讨论的是 Caffeine 实战,对 Window-TinyLFU 算法感兴趣的同学可以参考我写的《Caffeine缓存淘汰算法Window-TinyLFU详解》,不在此过多赘述。

为了简单起见,这里只展示两个最简单的缓存驱逐策略:基于空间驱逐和基于时间驱逐。

首先来看基于空间的驱逐策略。意思是给 Caffeine 设置固定的缓存数量上限,当缓存中的数据超过大小限制时,最先进入缓存的数据被驱逐 (先进先出) 。这部分的设置在构建 Caffeine 构建器 builder 时配置,下面是一段代码示例。

@Test
public void testEvictCache() throws InterruptedException {

    // 1.创建Caffeine Cache对象
    Cache<String, String> cache = Caffeine.newBuilder()
        // 1.1 设置缓存大小上限为1,即只能缓存1个数据
        .maximumSize(1)
        .build();

    // 2.缓存3个数据,故意超出上限
    cache.put("id1", "赵构");
    cache.put("id2", "秦桧");
    cache.put("id3", "岳飞");

    // 3.延迟10ms,给负责驱逐的线程一点时间
    Thread.sleep(10L);

    // 4.查询这3个数据
    System.out.println("id1 = " + cache.getIfPresent("id1"));
    System.out.println("id2 = " + cache.getIfPresent("id2"));
    System.out.println("id3 = " + cache.getIfPresent("id3"));
}

输出:

id1 = null
id2 = null
id3 = 岳飞

上面第 16 行代码,说明 Caffeine 驱逐缓存是需要一定时间的。如果不给驱逐线程留时间,则会全部 3 个数据都查出来。


最后来看基于时间的驱逐策略。意思是给每个缓存数据设置一个【有效时间】(time to live, TTL) ,超时则被驱逐。使用方法与上面相似,下面是使用代码示例。

@Test
public void testTTLBasedEvictCache() throws InterruptedException {

    // 1.创建Caffeine对象
    Cache<String, String> cache = Caffeine.newBuilder()
        // 1.1 设置有效时间为1s
        .expireAfterWrite(1, TimeUnit.SECONDS)
        .build();

    // 2.缓存数据
    cache.put("id1", "赵构");
    // 3.马上查询,可以看到数据
    System.out.println("id1 = " + cache.getIfPresent("id1"));

    // 4.故意延迟1.2s,验证缓存过期
    Thread.sleep(1200L);
    // 5.再次查询缓存,发现已过期
    System.out.println("id1 = " + cache.getIfPresent("id1"));
}

输出:

id1 = 赵构
id1 = null

以上就是 Caffeine 缓存最基本的使用方法了,希望对你有帮助。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐