• 本地缓存Caffeine


    文章来源:https://www.cnblogs.com/CrankZ/p/10889859.html

    说起Guava Cache,很多人都不会陌生,它是Google Guava工具包中的一个非常方便易用的本地化缓存实现,基于LRU算法实现,支持多种缓存过期策略。由于Guava的大量使用,Guava Cache也得到了大量的应用。但是,Guava Cache的性能一定是最好的吗?也许,曾经,它的性能是非常不错的。但所谓长江后浪推前浪,总会有更加优秀的技术出现。今天,我就来介绍一个比Guava Cache性能更高的缓存框架:Caffeine。

    Tips: Spring5(SpringBoot2)开始用Caffeine取代guava.详见官方信息SPR-13797
    https://jira.spring.io/browse/SPR-13797

    1|1什么时候用


    1. 愿意消耗一些内存空间来提升速度
    2. 预料到某些键会被多次查询
    3. 缓存中存放的数据总量不会超出内存容量

    1|2性能


    由图可以看出,Caffeine不论读还是写的效率都远高于其他缓存。

    这里只列出部分性能比较,详细请看官方官方 https://github.com/ben-manes/caffeine/wiki/Benchmarks

    1|3依赖


    我们需要在 pom.xml 中添加 caffeine 依赖:

    版本问题参考https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine

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

    1|4新建对象


    // 1、最简单 Cache<String, Object> cache = Caffeine.newBuilder() .build(); // 2、真实使用过程中我们需要自己配置参数。这里只列举部分,具体请看下面列表 Cache<String, Object> cache = Caffeine.newBuilder() .initialCapacity(2)//初始大小 .maximumSize(2)//最大数量 .expireAfterWrite(3, TimeUnit.SECONDS)//过期时间 .build();

    参数含义

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

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

    1|5异步


    AsyncCache<Object, Object> asyncCache = Caffeine.newBuilder() .buildAsync();

    解释

    A semi-persistent mapping from keys to values. Cache entries are manually added using {@link #get(Object, Function)} or {@link #put(Object, CompletableFuture)}, and are stored in the cache until either evicted or manually invalidated. Implementations of this interface are expected to be thread-safe, and can be safely accessed by multiple concurrent threads.

    1|6添加数据


    Caffeine 为我们提供了三种填充策略:

    手动、同步和异步

    手动添加

    很简单的

    public static void main(String[] args) { Cache<String, String> cache = Caffeine.newBuilder() .build(); cache.put("hello", "world"); System.out.println(cache.getIfPresent("hello")); }

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

    Cache<String, String> cache = Caffeine.newBuilder() .build(); // 1.如果缓存中能查到,则直接返回 // 2.如果查不到,则从我们自定义的getValue方法获取数据,并加入到缓存中 cache.get("hello", new Function<String, String>() { @Override public String apply(String k) { return getValue(k); } }); System.out.println(cache.getIfPresent("hello")); } // 缓存中找不到,则会进入这个方法。一般是从数据库获取内容 private static String getValue(String k) { return k + ":value";

    // 这种写法可以简化成下面Lambda表达式
    cache.get("hello", new Function<String, String>() {
    @Override
    public String apply(String k) {
    return getValue(k);
    }
    });
    // 可以简写为
    cache.get("hello", k -> getValue(k));

    自动添加2(初始添加)

    和上面方法一样,只不过这个是在新建对象的时候添加

    LoadingCache<String, String> loadingCache = Caffeine.newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String k) { return getValue(k); } }); // 同样可简化为下面这样 LoadingCache<String, String> loadingCache2 = Caffeine.newBuilder() .build(k -> getValue(k));

    1|7过期策略


    Caffeine提供三类驱逐策略:

    1. 基于大小(size-based)
    2. 基于时间(time-based)
    3. 基于引用(reference-based)

    1、大小

    Cache<String, String> cache = Caffeine.newBuilder() .maximumSize(3) .build(); cache.put("key1", "value1"); cache.put("key2", "value2"); cache.put("key3", "value3"); cache.put("key4", "value4"); cache.put("key5", "value5"); cache.cleanUp(); System.out.println(cache.getIfPresent("key1")); System.out.println(cache.getIfPresent("key2")); System.out.println(cache.getIfPresent("key3")); System.out.println(cache.getIfPresent("key4")); System.out.println(cache.getIfPresent("key5"));

    输出结果

    null value2 null value4 value5

    1、淘汰2个

    2、淘汰并不是按照先后顺序,内部有自己的算法

    2、时间

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

    • expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。
    • expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时,在指定的时间后过期。
    • expireAfter(Expiry): 自定义策略,过期时间由Expiry实现独自计算。

    缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)。

    expireAfterWrite

    Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(3, TimeUnit.SECONDS) .build(); cache.put("key1", "value1"); cache.put("key2", "value2"); cache.put("key3", "value3"); cache.put("key4", "value4"); cache.put("key5", "value5"); System.out.println(cache.getIfPresent("key1")); System.out.println(cache.getIfPresent("key2")); Thread.sleep(3*1000); System.out.println(cache.getIfPresent("key3")); System.out.println(cache.getIfPresent("key4")); System.out.println(cache.getIfPresent("key5"));

    结果

    value1 value2 null null null

    例子2

    Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(3, TimeUnit.SECONDS) .build(); cache.put("key1", "value1"); Thread.sleep(1*1000); System.out.println(cache.getIfPresent("key1")); Thread.sleep(1*1000); System.out.println(cache.getIfPresent("key1")); Thread.sleep(1*1000); System.out.println(cache.getIfPresent("key1"));

    结果

    value1 value1 null

    expireAfterAccess

    Access就是读和写

    Cache<String, String> cache = Caffeine.newBuilder() .expireAfterAccess(3, TimeUnit.SECONDS) .build(); cache.put("key1", "value1"); Thread.sleep(1*1000); System.out.println(cache.getIfPresent("key1")); Thread.sleep(1*1000); System.out.println(cache.getIfPresent("key1")); Thread.sleep(1*1000); System.out.println(cache.getIfPresent("key1")); Thread.sleep(3*1000); System.out.println(cache.getIfPresent("key1"));

    结果

    value1 value1 value1 null

    读和写都没有的情况下,3秒后才过期

    也可以同时用expireAfterAccess和expireAfterWrite方法指定过期时间,这时只要对象满足两者中的一个条件就会被自动过期删除。

    expireAfter 和 refreshAfter 之间的区别

    • expireAfter 条件触发后,新的值更新完成前,所有请求都会被阻塞,更新完成后其他请求才能访问这个值。这样能确保获取到的都是最新的值,但是有性能损失。
    • refreshAfter 条件触发后,新的值更新完成前也可以访问,不会被阻塞,只是获取的是旧的数据。更新结束后,获取的才是新的数据。有可能获取到脏数据。

    3、引用

    • Caffeine.weakKeys() 使用弱引用存储key。如果没有其他地方对该key有强引用,那么该缓存就会被垃圾回收器回收。
    • Caffeine.weakValues() 使用弱引用存储value。如果没有其他地方对该value有强引用,那么该缓存就会被垃圾回收器回收。
    • Caffeine.softValues() 使用软引用存储value。
    Cache<String, Object> cache = Caffeine.newBuilder() .weakValues() .build(); Object value1 = new Object(); Object value2 = new Object(); cache.put("key1", value1); cache.put("key2", value2); value2 = new Object(); // 原对象不再有强引用 System.gc(); System.out.println(cache.getIfPresent("key1")); System.out.println(cache.getIfPresent("key2"));

    结果

    java.lang.Object@7a4f0f29 null

    解释:当给value2引用赋值一个新的对象之后,就不再有任何一个强引用指向原对象。System.gc()触发垃圾回收后,原对象就被清除了。

    简单回顾下Java中的四种引用

    Java4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用

    引用类型被垃圾回收时间用途生存时间
    强引用 从来不会 对象的一般状态 JVM停止运行时终止
    软引用 在内存不足时 对象缓存 内存不足时终止
    弱引用 在垃圾回收时 对象缓存 GC运行后终止
    虚引用 Unknown Unknown Unknown

    1|8显式删除缓存


    除了通过上面的缓存淘汰策略删除缓存,我们还可以手动的删除

    // 1、指定key删除 cache.invalidate("key1"); // 2、批量指定key删除 List<String> list = new ArrayList<>(); list.add("key1"); list.add("key2"); cache.invalidateAll(list);//批量清除list中全部key对应的记录 // 3、删除全部 cache.invalidateAll();

    1|9淘汰、移除监听器


    可以为缓存对象添加一个移除监听器,这样当有记录被删除时可以感知到这个事件。

    Cache<String, String> cache = Caffeine.newBuilder() .expireAfterAccess(3, TimeUnit.SECONDS) .removalListener(new RemovalListener<Object, Object>() { @Override public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) { System.out.println("key:" + key + ",value:" + value + ",删除原因:" + cause); } }) .expireAfterWrite(1, TimeUnit.SECONDS) .build(); cache.put("key1", "value1"); cache.put("key2", "value2"); cache.invalidate("key1"); Thread.sleep(2 * 1000); cache.cleanUp();

    结果

    key:key1,value:value1,删除原因:EXPLICIT key:key2,value:value2,删除原因:EXPIRED

    1|10统计


    Cache<String, String> cache = Caffeine.newBuilder() .maximumSize(3) .recordStats() .build(); cache.put("key1", "value1"); cache.put("key2", "value2"); cache.put("key3", "value3"); cache.put("key4", "value4"); cache.getIfPresent("key1"); cache.getIfPresent("key2"); cache.getIfPresent("key3"); cache.getIfPresent("key4"); cache.getIfPresent("key5"); cache.getIfPresent("key6"); System.out.println(cache.stats());

    结果

    CacheStats{hitCount=4, missCount=2, loadSuccessCount=0, loadFailureCount=0, totalLoadTime=0, evictionCount=0, evictionWeight=0}

    除了结果输出的内容,CacheStats还可以获取如下数据。

    2|0参考


    http://oopsguy.com/2017/10/25/java-caching-caffeine/
    https://juejin.im/post/5b8df63c6fb9a019e04ebaf4
    https://www.jianshu.com/p/9a80c662dac4
    https://www.sohu.com/a/235729991_100109711
    https://www.cnblogs.com/yueshutong/p/9381540.html
    https://blog.csdn.net/qq_38974634/article/details/80650810
    https://blog.csdn.net/qq_32867467/article/details/82944506
    https://blog.csdn.net/grafx/article/details/80462628
    http://ifeve.com/google-guava-cachesexplained/

  • 相关阅读:
    小米笔试(09-15)
    58同城笔试(09-14)
    Java中的static总结
    【Linux】常用代码
    【项目部署】上线
    入职一年高校的体会
    小团队管理总结
    从windows到Linux的再次转身,这次是硬件开发
    了解到雪花算法
    CCAI观后
  • 原文地址:https://www.cnblogs.com/fanheyan/p/12979536.html
Copyright © 2020-2023  润新知