Caffeine是一种基于Java的本地缓存库,具有高性能和低延迟的特点。它是由Google开发的,旨在提供一种可靠和高效的本地缓存方案。在本篇博客中,我们将介绍如何使用Caffeine实现本地缓存。

Caffeine的特点

Caffeine提供了许多优秀的特性,使其成为Java中最受欢迎的本地缓存库之一。

Guava 中其读写操作夹杂着过期时间的处理,也就是你在一次 put 操作中有可能会做淘汰操作,所以其读写性能会受到一定影响。

Caffeine 在读写操作方面完爆 Guava,主要是因为 Caffeine 对这些事件的操作是异步的,将事件提交至队列(使用 Disruptor RingBuffer),然后会通过默认的 ForkJoinPool.commonPool(),或自己配置的线程池,进行取队列操作,然后再进行后续的淘汰、过期操作。

GuavaCache是基于LRU的,而Caffeine是基于LRU和LFU的,结合了两者的优点。对这两个算法不太清楚的同学,可以参考xjjdog之前的文章: 《3种堆内缓存算法,赠源码和设计思路》

两者合体之后,变成了新的W-TinyLFU算法,它的命中率非常高,内存占用更加的小,这是主要原因所在。

下面是一些Caffeine的主要特点:

高性能:Caffeine使用了许多优化技术来提高缓存的性能,如手动内存管理、内存对齐、无锁算法等。

低延迟:Caffeine的缓存操作通常具有非常低的延迟,可以在微秒级别完成。

容量限制:Caffeine支持设置缓存的容量限制,当缓存达到容量上限时,将使用一些缓存策略来替换缓存项。

过期机制:Caffeine支持使用时间和其他条件来定义缓存项的过期时间,从而自动清除不需要的缓存项。

统计信息:Caffeine可以提供有关缓存使用情况的详细统计信息,如缓存命中率、缓存项数等。

弱引用支持:Caffeine支持使用弱引用来缓存对象,这些对象可以在不再使用时自动释放。

多线程支持:Caffeine使用了许多线程安全技术来保证多线程访问的正确性和并发性。

Caffeine的使用

接下来我们将介绍如何使用Caffeine实现本地缓存。首先,我们需要引入Caffeine库,例如:

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

复制

然后,我们可以使用以下代码来创建一个缓存对象:

Cache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();

复制

在上面的代码中,我们使用了Caffeine.newBuilder()方法来创建一个Caffeine对象,然后使用maximumSize()方法设置缓存的最大容量为1000个缓存项,使用expireAfterWrite()方法设置缓存项的过期时间为10分钟。最后,我们使用build()方法创建了一个缓存对象。

接下来,我们可以使用以下代码向缓存中添加缓存项:

String key ="mykey";
Object value = newObject();
cache.put(key, value);
#根据key获取缓存值,如果不存在返回null,不会触发缓存加载和刷新操作事件
#getIfPresent(Key)
#根据key获取缓存值,如果不存在调用function方法,并放入缓存,
#function返回null或异常不更新缓存,触发缓存加载和刷新事件
get(key,function)
#用于获取一组key的缓存值,如果不存在,调用function并放入缓存,触发缓存加载和刷新事件
getAll(Iterable,function)
#用于获取一组key的缓存值,如果不存在忽略,不会触发缓存加载和刷新操作事件
getAll(Iterable)
//强制失效
invalidateAll();
#清理
cleanUp()

复制

在上面的代码中,我们使用了put()方法将一个缓存项添加到缓存

接下来,我们可以使用以下代码从缓存中获取缓存项:

String key ="mykey";
Object value = cache.getIfPresent(key);

复制

在上面的代码中,我们使用了getIfPresent()方法从缓存中获取指定的缓存项。如果缓存项不存在或已过期,该方法将返回null。

除了上述基本用法外,Caffeine还提供了许多其他有用的功能,如异步加载、监听器、缓存清除等。接下来我们将介绍其中的一些功能。

异步加载

Caffeine支持异步加载缓存项,可以在缓存项不存在时自动加载缓存项。例如,我们可以使用以下代码异步加载缓存项:

CacheLoader<String, Object> loader = new CacheLoader<String, Object>() {
    public Object load(String key) throws Exception {
        // 加载缓存项的逻辑
        return new Object();
    }
};
AsyncLoadingCache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .buildAsync(loader);

String key = "mykey";
CompletableFuture<Object> future = cache.get(key);
Object value = future.get();

复制

在上面的代码中,我们使用了CacheLoader接口定义了异步加载缓存项的逻辑,并使用buildAsync()方法创建了一个异步加载缓存对象。然后,我们使用get()方法从异步缓存中获取缓存项,该方法将返回一个CompletableFuture对象,可以在该对象上使用get()方法等待缓存项加载完成。

监听器

Caffeine提供了缓存监听器,可以监听缓存项的插入、更新和删除操作。例如,我们可以使用以下代码添加一个缓存监听器:

Cache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();
cache.asMap().addListener((key, value) -> {
    // 缓存项插入、更新或删除时的操作
});

复制

在上面的代码中,我们使用addListener()方法添加了一个缓存监听器,该监听器可以在缓存项插入、更新或删除时执行相应的操作。

缓存清除

Caffeine支持使用多种缓存清除策略,例如基于大小、时间、访问时间、写入时间等。例如,我们可以使用以下代码设置基于访问时间的缓存清除策略:

Cache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterAccess(10, TimeUnit.MINUTES)
    .build();

复制

在上面的代码中,我们使用了expireAfterAccess()方法设置缓存清除策略为基于访问时间的清除策略,缓存项将在最后一次访问后10分钟内被清除。

结论

Caffeine是一个高性能、低延迟的本地缓存库,它提供了丰富的功能和灵活的配置选项,可以满足各种缓存场景的需求。在使用Caffeine时,我们需要根据实际情况选择合适的缓存策略和配置选项,以达到最优的性能和效果。

封装

package com.test.utils.caffeine;

import com.github.benmanes.caffeine.cache.stats.CacheStats;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

/**
 * @description: Caffeine封装接口
 * @author: Fangwj
 * @date: 2023/6/25
 */
public interface CaffeineCache<K, V> {

    /**
     * put
     *
     * @param key
     * @param value
     */
    void put(K key, V value);

    /**
     * get
     *
     * @param key
     * @return
     */
    V get(K key);

    /**
     * 判断是否包含K
     *
     * @param key
     * @return
     */
    boolean containsKey(K key);

    /**
     * 判断是否包含V
     *
     * @param value
     * @return
     */
    boolean containsValue(V value);

    /**
     * 移除某个K
     *
     * @param key
     */
    void remove(Object key);

    /**
     * 查询缓存命中,驱逐等数量
     *
     * @return
     */
    CacheStats cacheStats();

    /**
     * 清除全部(性能较慢,考虑场景使用)
     */
    void clear();

    /**
     * 转成MAP
     *
     * @return
     */
    ConcurrentMap<K, V> asMap();

    /**
     * 获取values
     *
     * @return
     */
    Collection<V> values();

    /**
     * 获取缓存大小
     *
     * @return
     */
    long size();

    /**
     * 主动回收已失效的缓存
     *
     * @return
     */
    void cleanUp();

    /**
     * 当缓存中有这个key就使用key对应的value值 如果没有就使用默认的value
     *
     * @return
     */
    V getOrDefault(K k, V v);

    /**
     * entrySet
     *
     * @return
     */
    Set<Map.Entry<K, V>> entrySet();
}

package com.test.utils.caffeine;

import com.github.benmanes.caffeine.cache.*;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * @description: Caffeine封装API实现类
 * @author: Fangwj
 * @date: 2023/6/25
 */
@Slf4j
public class CaffeineCacheLocal<K, V> implements CaffeineCache<K, V> {
    private final Cache<K, V> localCache;

    private RemovalListener<? super K, ? super V> removalListener;
    private long maximumSize = -1L;
    private long duration = -1L;
    private TimeUnit unit;

    public CaffeineCacheLocal() {
        localCache = initCache();
    }

    public CaffeineCacheLocal(RemovalListener<? super K, ? super V> removalListener, long maximumSize, long duration, TimeUnit unit) {
        if (removalListener != null) {
            this.removalListener = removalListener;
        }
        if (unit != null) {
            this.unit = unit;
        }
        this.duration = duration;
        this.maximumSize = maximumSize;
        this.localCache = initCache();
    }

    /**
     * 初始化
     *
     * @return
     */
    private Cache<K, V> initCache() {
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder();
        //暂时未加入权重逻辑 所以maximumSize必须设定
        //若加入权重逻辑后,可以根据是否有权重判断处理
        if (this.maximumSize <= 0L) {
            throw new RuntimeException("maximumSize is must be set");
        }

        //key的最大size
        caffeine.maximumSize(this.maximumSize);

        //expireAfterWrite全局时间淘汰策略
        if (this.duration > 0L && this.unit != null) {
            caffeine.expireAfterWrite(this.duration, this.unit);
        }

        //开启淘汰监听
        if (this.removalListener != null) {
            caffeine.removalListener(this.removalListener);
        }

        return caffeine.build();
    }

    @Override
    public void put(K key, final V value) {
        localCache.put(key, value);
    }

    @Override
    public V get(K key) {
        if (Objects.nonNull(key)){
            return localCache.getIfPresent(key);
        }
        return null;
    }

    @Override
    public boolean containsKey(K key) {
        return asMap().containsKey(key);
    }

    @Override
    public boolean containsValue(V value) {
        return asMap().containsValue(value);
    }

    @Override
    public void remove(Object key) {
        localCache.invalidate(key);
    }

    @Override
    public CacheStats cacheStats() {
        return localCache.stats();
    }

    @Override
    public void clear() {
        localCache.invalidateAll();
    }

    @Override
    public ConcurrentMap<K, V> asMap() {
        return localCache.asMap();
    }

    @Override
    public Collection<V> values() {
        return asMap().values();
    }

    @Override
    public long size() {
        return localCache.estimatedSize();
    }

    @Override
    public void cleanUp() {
        localCache.cleanUp();
    }

    @Override
    public V getOrDefault(K k, V defaultValue) {
        V v;
        return ((v = get(k)) != null) ? v : defaultValue;
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        return asMap().entrySet();
    }

    public static Builder<Object, Object> newBuilder() {
        return new Builder<>();
    }

    public static class Builder<K1, V1> {
        private RemovalListener<? super K1, ? super V1> removalListener;
        private long maximumSize;
        private long duration;
        private TimeUnit unit;

        public Builder<K1, V1> removalListener(RemovalListener removalListener) {
            this.removalListener = removalListener;
            return this;
        }

        public Builder<K1, V1> maximumSize(long maximumSize) {
            this.maximumSize = maximumSize;
            return this;
        }

        public Builder<K1, V1> expireAfterWrite(long duration, TimeUnit unit) {
            this.duration = duration;
            this.unit = unit;
            return this;
        }

        public <K extends K1, V extends V1> CaffeineCache<K, V> build() {
            return new CaffeineCacheLocal<>(removalListener, maximumSize, duration, unit);
        }
    }
}

由于自己的业务应用场景,此实现类只支持根据maximumSize最大限制和expireAfterWrite时间过期淘汰策略,

使用

    /**
       * 全局设定失效时间
       */
    @Test
    @Ignore
    public void expireAfterWriteTest() {
        CaffeineCache<Long, Set<String>> caffeineCache = CaffeineCacheLocal.newBuilder()
                       .maximumSize(100)
                       .expireAfterWrite(5, TimeUnit.SECONDS)
                       .removalListener(new RemovalListener<Long, Set<String>>() {
                           @Override
                           public void onRemoval(@Nullable Long key, @Nullable Set<String> value, @NonNull RemovalCause removalCause) {
                               System.out.println("移除了key:" + key + "  value:" + value + " cause:" + removalCause);
                           }
                       })
                       .build();
        caffeineCache.put(1L, Sets.newHashSet("1", "2"));
        caffeineCache.put(2L, Sets.newHashSet("3", "4"));
        System.out.println(caffeineCache.get(2L));
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(caffeineCache.get(2L));
    }

文章作者: 刘同学
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 刘同学的小站
后端
喜欢就支持一下吧