Caffeine高性能本地缓存使用
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));
}