本地缓存Caffeine Cache

DB + Redis + LocalCache = 高效存储,高效访问

Caffeine简介

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

它主要有以下几个功能:

  • 自动将条目加载到缓存中,可以选择同步或异步加载
  • 基于频率和新近度超过最大值时基于大小的逐出
  • 自上次访问或上次写入以来测得的基于时间的条目到期
  • 发生第一个陈旧的条目请求时,异步刷新
  • 键自动包装在弱引用中
  • 值自动包装在弱引用或软引用中
  • 逐出(或以其他方式删除)条目的通知
  • 写入通知
  • 缓存访问统计信息的

快速入门

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

pom 依赖

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

创建对象

Cache<String, Object> cache = Caffeine.newBuilder()
               .initialCapacity(100)//初始大小
               .maximumSize(200)//最大数量
               .expireAfterWrite(3, TimeUnit.SECONDS)//过期时间
               .build();

参数介绍:

  • initialCapacity:初始的缓存空间大小
  • maximumSize:缓存的最大数量
  • maximumWeight:缓存的最大权重
  • expireAfterAccess:最后一次读或写操作后经过指定时间过期
  • expireAfterWrite:最后一次写操作后经过指定时间过期
  • refreshAfterWrite:创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存
  • weakKeys:打开key的弱引用
  • weakValues:打开value的弱引用
  • softValues:打开value的软引用
  • recordStats:开发统计功能

注意:

  • expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。
  • maximumSize和maximumWeight不可以同时使用。

添加数据

Caffeine 为我们提供了手动、同步和异步这几种填充策略。

Cache<String, String> cache = Caffeine.newBuilder()
		.build();
cache.put("java金融", "java金融");
System.out.println(cache.getIfPresent("java金融"));

自动添加(自定义添加函数)

public static void main(String[] args) {
    Cache<String, String> cache = Caffeine.newBuilder()
        .build();
    // 1.如果缓存中能查到,则直接返回
    // 2.如果查不到,则从我们自定义的getValue方法获取数据,并加入到缓存中
    String val = cache.get("java金融", k -> getValue(k));
    System.out.println(val);
}

/**
 * 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
 *
 * @param k
 * @return
 */
private static String getValue(String k) {
    return k + ":value";
}

过期策略

Caffeine 为我们提供了三种过期策略,分别是基于大小(size-based)、基于时间(time-based)、基于引用(reference-based)

基于大小(size-based)

public static void main(String[] args) {
    LoadingCache<String, String> cache = Caffeine.newBuilder()
        // 最大容量为1
        .maximumSize(1)
        .build(k -> getValue(k));
    cache.put("java金融1", "java金融1");
    cache.put("java金融2", "java金融2");
    cache.put("java金融3", "java金融3");
    cache.cleanUp();
    System.out.println(cache.getIfPresent("java金融1"));
    System.out.println(cache.getIfPresent("java金融2"));
    System.out.println(cache.getIfPresent("java金融3"));
}

/**
 * * 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
 * * @param k
 * * @return
 */
private static String getValue(String k) {
    return k + ":value";
}

运行结果如下:淘汰了两个只剩下一个。

null
null
java金融3

基于时间(time-based)

Caffeine提供了三种定时驱逐策略:

expireAfterWrite(long, TimeUnit)

在最后一次写入缓存后开始计时,在指定的时间后过期。

public static void main(String[] args) throws Exception{
    LoadingCache<String, String> cache =  Caffeine.newBuilder()
        // 最大容量为1
        .maximumSize(1)
        .expireAfterWrite(3, TimeUnit.SECONDS)
        .build(k->getValue(k));
    cache.put("java金融","java金融");
    Thread.sleep(1*1000);
    System.out.println(cache.getIfPresent("java金融"));
    Thread.sleep(1*1000);
    System.out.println(cache.getIfPresent("java金融"));
    Thread.sleep(1*1000);
    System.out.println(cache.getIfPresent("java金融"));
}

/**
 * * 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
 * * @param k
 * * @return
 */
private static String getValue(String k) {
    return k + ":value";
}

运行结果第三秒的时候取值为空:

java金融
java金融
null

expireAfterAccess

在最后一次读或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。

public static void main(String[] args) throws Exception {
    LoadingCache<String, String> cache = Caffeine.newBuilder()
        // 最大容量为1
        .maximumSize(1)
        .expireAfterAccess(3, TimeUnit.SECONDS)
        .build(k -> getValue(k));
    cache.put("java金融", "java金融");
    Thread.sleep(1 * 1000);
    System.out.println(cache.getIfPresent("java金融"));
    Thread.sleep(1 * 1000);
    System.out.println(cache.getIfPresent("java金融"));
    Thread.sleep(1 * 1000);
    System.out.println(cache.getIfPresent("java金融"));
    Thread.sleep(3001);
    System.out.println(cache.getIfPresent("java金融"));
}

/**
 * * 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
 * * @param k
 * * @return
 */
private static String getValue(String k) {
    return k + ":value";
}

运行结果:读和写都没有的情况下,3秒后才过期,然后就输出了null。

java金融
java金融
java金融
null

expireAfter(Expiry)

在expireAfter中需要自己实现Expiry接口,这个接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之后多久过期。注意这个是和expireAfterAccess、expireAfterAccess是互斥的。这里和expireAfterAccess、expireAfterAccess不同的是,需要你告诉缓存框架,它应该在具体的某个时间过期,获取具体的过期时间。 

public static void main(String[] args) throws Exception {
    LoadingCache<String, String> cache = Caffeine.newBuilder()
        // 最大容量为1
        .maximumSize(1)
        .removalListener((key, value, cause) ->
                         System.out.println("key:" + key + ",value:" + value + ",删除原因:" + cause))
        .expireAfter(new Expiry<String, String>() {
            @Override
            public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {
                return currentTime;
            }

            @Override
            public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
                return currentTime;
            }

            @Override
            public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
                return currentTime;
            }
        })
        .build(k -> getValue(k));
}

/**
 * * 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
 * * @param k
 * * @return
 */
private static String getValue(String k) {
    return k + ":value";
}

删除

  • 单个删除:Cache.invalidate(key)
  • 批量删除:Cache.invalidateAll(keys)
  • 删除所有缓存项:Cache.invalidateAll

 

posted @ 2022-01-05 08:28  残城碎梦  阅读(1424)  评论(0编辑  收藏  举报