Caffeine本地缓存实战

Zeran

什么是本地缓存

本地缓存是指将客户机本地的物理内存划分出一部分空间用来缓冲客户机回写到服务器的数据,因其在回写上的突出贡献,因此本地缓存一般称为本地回写。本地缓存概念首次出现是在无盘领域,作为PXD无盘的一项核心技术被广泛应用。
作用:该技术将客户机回写的数据不再先写入服务器硬盘,而是将回写数据先写入本地回写缓存,当缓存空间达到一定的阈值时,再将数据回写到服务器。有了本地回写缓存功能之后,可大大降低服务器读写压力和网络负载。

Caffeine 简介

Caffeine 是一个高性能Java 缓存库,提供近乎最佳的命中率。
A Cache 类似于ConcurrentMap,但不完全相同。最根本的区别是ConcurrentMap保留所有添加到它的元素,直到它们被显式删除。Cache另一方面,A通常配置为自动驱逐条目,以限制其内存占用。在某些情况下, a LoadingCacheorAsyncLoadingCache可能很有用,即使它不驱逐条目,由于其自​​动缓存加载

Caffeine 功能

Caffeine 提供灵活的构造来创建具有以下功能组合的缓存:

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

Caffeine 使用

1.pom.xml依赖

            <!--caffeine 本地缓存-->
            <dependency>
                <groupId>com.github.ben-manes.caffeine</groupId>
                <artifactId>caffeine</artifactId>
                <version>2.9.1</version>
            </dependency>

2.初始化缓存

你也可以基于以下,做升级,比如做异步操作...

/**
 * @description Caffeine本地缓存
 * @author: yx-0173
 * @date: 2021-07-15 10:23
 **/
@Configuration
@Slf4j
public class CacheConfig {

    @Bean
    public Cache<String, Object> cache() {
        return Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // 监听器
                .removalListener((String key, Object graph, RemovalCause cause) ->
                        log.info("移除缓存中key为:{},value为:{}的缓存记录", key, graph))
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(1000)
                .build();
    }
   
   // api测试,以下代码不要写道这个配置类,可自行整理
   
   // 查找条目,如果未找到,则为null
   cache.getIfPresent(key);
   // 从缓存中查询,如果查询不到则将对应的值放入缓存,如果不可计算,则为null
   cache.get(key, k - > createExpensiveGraph(key));
   // 插入或修改缓存
   cache.put(key, graph);
   // 根据key删除一个缓存
   cache.invalidate(key);
   // 批量获取
   cache.getAll(keys);
   。。。还有很多,可自行官网,页尾附上官网地址
}

3.工具类贡献给大家

**
 * @description 本地缓存工具类
 * @author: yx-0173
 * @date: 2021-07-15 12:51
 **/
@Component
public class CacheUtil<K, V> {

    @Resource
    private Cache<K, V> cache;

    /**
     * 根据key获取value
     *
     * @return Object
     */
    public V get(K key) {
        return cache.asMap().get(key);
    }

    /**
     * 根据key获取value
     *
     * @return Object
     */
    public Map<? extends K, ? extends V> getBatch(List<String> key) {
        return cache.getAllPresent(key);
    }

    /**
     * 将一个map插入
     */
    public void putBatch(Map<? extends K, ? extends V> map) {
        cache.asMap().putAll(map);
    }


    public ConcurrentMap<K, V> get() {
        return cache.asMap();
    }

    /**
     * 插入缓存
     *
     * @param key   key
     * @param value value
     */
    public void put(K key, V value) {
        cache.put(key, value);
    }

    /**
     * 插入缓存,如果不存在,则将value放入缓存
     *
     * @param key   key
     * @param value value
     */
    public V getIfNotExist(K key, V value) {
        return cache.get(key, k -> value);
    }

    /**
     * 是否含有
     *
     * @param key key
     */
    public boolean contains(K key) {
        return cache.asMap().containsKey(key);
    }

    /**
     * 清除
     */
    public void deleteAll() {
        cache.invalidateAll();
    }

    /**
     * 批量删除
     *
     * @param key key
     */
    public void delete(List<String> key) {
        cache.invalidateAll(key);
    }

    /**
     * 删除
     *
     * @param key key
     */
    public void delete(K key) {
        cache.asMap().remove(key);
    }

    /**
     * 更新
     *
     * @param key   key
     * @param value value
     */
    public void update(K key, V value) {
        cache.put(key, value);
    }
}

Caffeine驱逐策略

1.基于大小

Size-based

// 根据缓存中的条目数驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .build(key -> createExpensiveGraph(key));

// 根据缓存中的顶点数驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumWeight(10_000)
    .weigher((Key key, Graph graph) -> graph.vertices().size())
    .build(key -> createExpensiveGraph(key));

2.基于时间

// 基于固定的过期策略进行
驱逐 LoadingCache< Key , Graph > graphs =  Caffeine . newBuilder()
    .expireAfterAccess( 5 , TimeUnit . MINUTES )
    .build(key - > createExpensiveGraph(key));
LoadingCache< Key , Graph > graphs =  Caffeine 。newBuilder()
    .expireAfterWrite( 10 , TimeUnit . MINUTES )
    .build(key - > createExpensiveGraph(key));

// 根据不同的过期策略进行
驱逐 LoadingCache< Key , Graph > graphs =  Caffeine . newBuilder()
    .expireAfter( new  Expiry< Key , Graph > () {
       public  long  expireAfterCreate ( Key  key , Graph  graph , long  currentTime ) {
         // 如果来自外部资源,则使用挂钟时间而不是纳米时间
        long seconds = graph . creationDate( ) . plusHours( 5 )
            .minus(系统。的currentTimeMillis(),MILLIS)
            .toEpochSecond();
        返回 TIMEUNIT 。秒。toNanos(秒);
      }
      public  long  expireAfterUpdate ( Key  key , Graph  graph , 
           long  currentTime , long  currentDuration ) {
         return currentDuration;
      }
      public  long  expireAfterRead ( Key  key , Graph  graph ,
           long  currentTime , long  currentDuration ) {
         return currentDuration;
      }
    })
    .build(key - > createExpensiveGraph(key));

expireAfterAccess(long, TimeUnit):自上次通过读取或写入访问条目后经过指定的持续时间后使条目过期。如果缓存数据绑定到会话并由于不活动而过期,这可能是可取的。
expireAfterWrite(long, TimeUnit):自创建条目或最近的值替换后指定的持续时间过后,使条目过期。如果缓存数据在一段时间后变得陈旧,这可能是可取的。
expireAfter(Expiry):在可变持续时间过后使条目过期。如果条目的过期时间由外部资源确定,这可能是可取的。
在写入期间和偶尔在读取期间通过定期维护执行到期。到期事件的调度和触发是在 O(1) 的分摊时间内执行的。

为了提示过期,而不是依赖其他缓存活动来触发日常维护,使用Scheduler接口和Caffeine.scheduler(Scheduler)方法在你的缓存构建器中指定一个调度线程。Java 9+ 用户可能更喜欢使用Scheduler.systemScheduler()专用的、系统范围的调度线程。

测试定时驱逐不需要测试等到挂钟时间过去。使用Ticker接口和Caffeine.ticker(Ticker)方法在缓存构建器中指定时间源,而不必等待系统时钟。Guava 的 testlibFakeTicker为这个目的提供了便利。

3.基于参考

//当键和值都不是强可达时
驱逐 LoadingCache< Key , Graph > graphs =  Caffeine . newBuilder() .weakKeys()
    .weakValues()
    .build(
    key - > createExpensiveGraph(key)); //当垃圾收集器需要释放内存时驱逐 LoadingCache< Key , Graph > graphs = Caffeine . newBuilder() .softValues()
    .build(
    key - > createExpensiveGraph(key));

Caffeine 允许您通过对键或值使用弱引用以及对值使用软引用来设置缓存以允许条目的垃圾收集。请注意,不支持弱值和软值引用AsyncCache。

Caffeine.weakKeys()使用弱引用存储键。如果没有其他对键的强引用,这允许条目被垃圾收集。由于垃圾收集仅取决于身份相等性,这会导致整个缓存使用身份 (==) 相等性来比较键,而不是equals()。

Caffeine.weakValues()使用弱引用存储值。如果没有其他对值的强引用,这允许对条目进行垃圾收集。由于垃圾收集仅取决于身份相等性,这会导致整个缓存使用身份 (==) 相等性来比较值,而不是equals()。

Caffeine.softValues()使用软引用存储值。软引用的对象以全局最近最少使用的方式进行垃圾收集,以响应内存需求。由于使用软引用的性能影响,我们通常建议使用更可预测的最大缓存大小。使用 ofsoftValues()将导致使用标识 (==) 相等而不是equals().

学习之道,永无止境,要学习如何以渔,不要学如何以鱼。
Caffeine官网:https://github.com/ben-manes/...

阅读 370

学而不思则罔,思而不学则殆。

1 声望
2 粉丝
0 条评论
你知道吗?

学而不思则罔,思而不学则殆。

1 声望
2 粉丝
宣传栏