本文正在参与「金石计划」

内存缓存Caffeine基本使用姿势介绍

Caffeine作为当下本地缓存的王者被很多的运用再实践的项目中,可以有效的提高服务吞吐率、qps,降低rt

本文将简单介绍下Caffeine的运用姿态

Spring专栏】内存缓存Caffeine根本运用姿态-技术派

项目装备

1. 依靠

首要搭建一个标准的SpringBoot项目工程,相关版别以及依靠如下

本项目凭借SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

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

运用实例

引进上面的jar包之后,就可以进入caffeine的运用环节了;咱们首要按照官方wiki来进行演练

  • Home zh CN ben-manes/caffeine Wiki

caffeine供给了四种缓存战略,首要是依据手动增加/主动增加,同步/异步来进行区分

其根本运用姿态于Guava差不多

1. 手动加载

private LoadingCache<String, Integer> autoCache;
private AtomicInteger idGen;
public CacheService() {
      // 手动缓存加载方法
      idGen = new AtomicInteger(100);
      uidCache = Caffeine.newBuilder()
              // 设置写入后五分钟失效
              .expireAfterWrite(5, TimeUnit.MINUTES)
              // 设置最多的缓存数量
              .maximumSize(100)
              .build();
}

1.1 三种失效战略

留意参数设置,咱们先看一下失效战略,共有下面几种

权重

  • maximumSize: 依据容量战略,当缓存内元素个数超过时,经过依据就近度和频率的算法来驱逐掉不会再被运用到的元素
  • maximumWeight: 依据权重的容量战略,首要运用于缓存中的元素存在不同的权重场景

时刻:

  • expireAfterAccess: 依据访问时刻
  • expireAfterWrite: 依据写入时刻
  • expireAfter: 可以依据读更新写入来调整有效期

引证:

  • weakKeys: 保存的key为弱引证
  • weakValues: 保存的value会运用弱引证
  • softValues: 保存的value运用软引证

弱引证:这允许在GC的过程中,当没有被任何强引证指向的时分去将缓存元素收回

软引证:在GC过程中被软引证的目标将会被经过LRU算法收回

1.2 缓存增删查姿态

接下来咱们看一下手动方法的运用

public void getUid(String session) {
    // 从头再取一次,这次应该就不是从头初始化
    Integer uid = uidCache.getIfPresent(session);
    System.out.println("检查缓存! 当没有的时分回来的是 uid: " + uid);
    // 第二个参数表明当不存在时,初始化一个,并写入缓存中
    uid = uidCache.get(session, (key) -> 10);
    System.out.println("初始化一个之后,回来的是: " + uid);
    // 移除缓存
    uidCache.invalidate(session);
    // 手动增加一个缓存
    uidCache.put(session + "_2", 11);
    // 检查一切的额缓存
    Map map = uidCache.asMap();
    System.out.println("total: " + map);
    // 干掉一切的缓存
    uidCache.invalidateAll();
}

查询缓存&增加缓存

  • getIfPresent(key): 不存在时,回来null
  • get(key, (key) -> {value初始化战略}): 不存在时,会依据第二个lambda表达式来写入数据,这个就表明的是手动加载缓存
  • asMap: 获取缓存一切数据

增加缓存

  • put(key, val): 主动增加缓存

清空缓存

  • invalidate: 主动移除缓存
  • invalidateAll: 失效一切缓存

履行结束之后,输出日志:

检查缓存! 当没有的时分回来的是 uid: null
初始化一个之后,回来的是: 10
total: {02228476-bcd9-412d-b437-bf0092c4a5f6_2=11}

2. 主动加载

在创建的时分,就指定缓存未命中时的加载规矩

// 在创建时,主动指定加载规矩
private LoadingCache<String, Integer> autoCache;
private AtomicInteger idGen;
public CacheService() {
    // 手动缓存加载方法
    idGen = new AtomicInteger(100);
    autoCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .maximumSize(100)
            .build(new CacheLoader<String, Integer>() {
                @Override
                public @Nullable Integer load(@NonNull String key) throws Exception {
                    return idGen.getAndAdd(1);
                }
            });
}

它的装备,与前面介绍的共同;首要的差异点在于build时,确认缓存值的获取方法

2.1 缓存运用姿态

public void autoGetUid(String session) {
    Integer uid = autoCache.getIfPresent(session);
    System.out.println("主动加载,没有时回来: " + uid);
    uid = autoCache.get(session);
    System.out.println("主动加载,没有时主动加载一个: " + uid);
    // 批量查询
    List<String> keys = Arrays.asList(session, session + "_1");
    Map<String, Integer> map = autoCache.getAll(keys);
    System.out.println("批量获取,一个存在一个不存在时:" + map);
    // 手动加一个
    autoCache.put(session + "_2", 11);
    Map total = autoCache.asMap();
    System.out.println("total: " + total);
}

与前面的差异在于获取缓存值的方法

  • get(key): 不用传第二个参数,直接传key获取对应的缓存值,如果没有主动加载数据
  • getAll(keys): 可以批量获取数据,若某个key不再缓存中,会主动加载;在里面的则直接运用缓存的

实践输出结果如下

主动加载,没有时回来: null
主动加载,没有时主动加载一个: 100
批量获取,一个存在一个不存在时:{02228476-bcd9-412d-b437-bf0092c4a5f6=100, 02228476-bcd9-412d-b437-bf0092c4a5f6_1=101}
total: {02228476-bcd9-412d-b437-bf0092c4a5f6_2=11, 02228476-bcd9-412d-b437-bf0092c4a5f6_1=101, 02228476-bcd9-412d-b437-bf0092c4a5f6=100}

3.异步手动加载

异步,首要是值在获取换粗内容时,选用的异步战略;运用与前面没有什么太大差别

// 手动异步加载缓存
private AsyncCache<String, Integer> asyncUidCache;
public CacheService() {
    asyncUidCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .maximumSize(100)
            .buildAsync();
}

3.1 缓存运用姿态

public void asyncGetUid(String session) throws ExecutionException, InterruptedException {
    // 从头再取一次,这次应该就不是从头初始化了
    CompletableFuture<Integer> uid = asyncUidCache.getIfPresent(session);
    System.out.println("检查缓存! 当没有的时分回来的是 uid: " + (uid == null ? "null" : uid.get()));
    // 第二个参数表明当不存在时,初始化一个,并写入缓存中
    uid = asyncUidCache.get(session, (key) -> 10);
    System.out.println("初始化一个之后,回来的是: " + uid.get());
    // 手动塞入一个缓存
    asyncUidCache.put(session + "_2", CompletableFuture.supplyAsync(() -> 12));
    // 移除缓存
    asyncUidCache.synchronous().invalidate(session);
    // 检查一切的额缓存
    System.out.println("print total cache:");
    for (Map.Entry<String, CompletableFuture<Integer>> sub : asyncUidCache.asMap().entrySet()) {
        System.out.println(sub.getKey() + "==>" + sub.getValue().get());
    }
    System.out.println("total over");
}
  • getIfPresent: 存在时回来CompletableFuture,不存在时回来null,因此留意npe的问题
  • get(key, Function<>): 第二个参数表明加载数据的逻辑
  • put(key, CompletableFuture<>): 手动参加缓存,留意这里也不是直接加一个详细的value到缓存
  • synchronous().invalidate() : 同步清除缓存
  • getAll: 一次获取多个缓存,同样的是在缓存的取缓存,不在的依据第二个传参进行加载

与前面相比,运用姿态差不多,仅有留意的是,获取的并不是直接的结果,而是CompletableFuture,上面履行之后的输出如下:

检查缓存! 当没有的时分回来的是 uid: null
初始化一个之后,回来的是: 10
print total cache:
5dd53310-aec7-42a5-957e-f7492719c29d_2==>12
total over

4. 异步主动加载

在定义缓存时,就指定了缓存不存在的加载逻辑;与第二个相比差异在于这里是异步加载数据到缓存中

private AtomicInteger idGen;
// 主动异步加载缓存
private AsyncLoadingCache<String, Integer> asyncAutoCache;
public CacheService() {
  idGen = new AtomicInteger(100);
  asyncAutoCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .maximumSize(100)
            .buildAsync(new CacheLoader<String, Integer>() {
                @Override
                public @Nullable Integer load(@NonNull String key) throws Exception {
                    return idGen.getAndAdd(1);
                }
            });
}

4.1 缓存运用姿态

public void asyncAutoGetUid(String session) {
    try {
        CompletableFuture<Integer> uid = asyncAutoCache.getIfPresent(session);
        System.out.println("主动加载,没有时回来: " + (uid == null ? "null" : uid.get()));
        uid = asyncAutoCache.get(session);
        System.out.println("主动加载,没有时主动加载一个: " + uid.get());
        // 批量查询
        List<String> keys = Arrays.asList(session, session + "_1");
        CompletableFuture<Map<String, Integer>> map = asyncAutoCache.getAll(keys);
        System.out.println("批量获取,一个存在一个不存在时:" + map.get());
        // 手动加一个
        asyncAutoCache.put(session + "_2", CompletableFuture.supplyAsync(() -> 11));
        // 检查一切的额缓存
        System.out.println("print total cache:");
        for (Map.Entry<String, CompletableFuture<Integer>> sub : asyncAutoCache.asMap().entrySet()) {
            System.out.println(sub.getKey() + "==>" + sub.getValue().get());
        }
        System.out.println("total over");
        // 清空一切缓存
        asyncAutoCache.synchronous().invalidateAll();
      } catch (Exception e) {
        e.printStackTrace();
    }
}

输出:

主动加载,没有时回来: null
主动加载,没有时主动加载一个: 102
批量获取,一个存在一个不存在时:{5dd53310-aec7-42a5-957e-f7492719c29d=102, 5dd53310-aec7-42a5-957e-f7492719c29d_1=103}
print total cache:
5dd53310-aec7-42a5-957e-f7492719c29d_2==>11
5dd53310-aec7-42a5-957e-f7492719c29d_1==>103
5dd53310-aec7-42a5-957e-f7492719c29d==>102
total over

不能错失的源码和相关知识点

0. 项目

  • 工程:github.com/liuyueyi/sp…
  • 源码:github.com/liuyueyi/sp…

1. 微信大众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,不免有遗漏和过错之处,如发现bug或许有更好的主张,欢迎批评指正,不惜感激

下面一灰灰的个人博客,记载一切学习和工作中的博文,欢迎大家前去逛逛

  • 一灰灰Blog个人博客 blog.hhui.top
  • 一灰灰Blog-Spring专题博客 spring.hhui.top