Caffeine是一个高性能的本地缓存框架,缓存存储底层依赖JDK的ConcurrentHashMap。支持设置缓存过期时间等功能。
com.github.ben-manes.caffeine
caffeine
2.9.2
下面是创建缓存实例举例。
/**
* 创建Caffeine缓存实例
*/
@Configuration
public class CaffeineConfig {
/**
* 通用Caffeine缓存的过期时间。单位:s.
*/
@Value("${expireTime.caffeineCacheOfCommon:5}")
private Integer expireTime4CaffeineCacheOfCommon;
@Bean("caffeineCacheOfCommon")
public Cache caffeineCacheOfCommon() {
return Caffeine.newBuilder()
// 设置创建缓存或者最后一次更新缓存后,经过固定时间后数据过期
.expireAfterWrite(expireTime4CaffeineCacheOfCommon, TimeUnit.SECONDS)
.build();
}
}
@Resource
private TestDubboService testDubboService;
@Resource
private Cache caffeineCacheOfCommon;
public List queryDto(String province) {
if (StringUtils.isBlank(province)) {
return new ArrayList<>();
}
try {
List testList = (List) caffeineCacheOfCommon.getIfPresent(province);
if (testList != null) {
return testList;
}
testList = testDubboService.queryDtoByProvince(province);
if (testList == null) {
return new ArrayList<>();
}
caffeineCacheOfCommon.put(province, testList);
return testList;
} catch (Exception e) {
log.error("queryDto error.", e);
}
return new ArrayList<>();
}
获取缓存数据时,根据当前时间、缓存写入时间(或缓存访问时间、截止时间)和缓存过期时间,判断缓存是否过期。如果已过期,则异步执行过期缓存删除任务。
// 获取缓存数据
public @Nullable V getIfPresent(Object key, boolean recordStats) {
Node node = data.get(nodeFactory.newLookupKey(key));
if (node == null) {
if (recordStats) {
statsCounter().recordMisses(1);
}
if (drainStatus() == REQUIRED) {
scheduleDrainBuffers();
}
return null;
}
V value = node.getValue();
long now = expirationTicker().read();
// 判断缓存是否过期或是否已被垃圾回收
if (hasExpired(node, now) || (collectValues() && (value == null))) {
if (recordStats) {
statsCounter().recordMisses(1);
}
// 异步执行过期缓存删除任务
scheduleDrainBuffers();
return null;
}
if (!isComputingAsync(node)) {
@SuppressWarnings("unchecked")
K castedKey = (K) key;
setAccessTime(node, now);
tryExpireAfterRead(node, castedKey, value, expiry(), now);
}
afterRead(node, now, recordStats);
return value;
}
// 1、判断缓存是否过期
// now-当前时间,node.getWriteTime-缓存写入时间,expiresAfterWriteNanos-缓存过期时间
boolean hasExpired(Node node, long now) {
return (expiresAfterAccess() && (now - node.getAccessTime() >= expiresAfterAccessNanos()))
| (expiresAfterWrite() && (now - node.getWriteTime() >= expiresAfterWriteNanos()))
| (expiresVariable() && (now - node.getVariableTime() >= 0));
}
// 2、异步执行过期缓存删除任务。drainBuffersTask为PerformCleanupTask
void scheduleDrainBuffers() {
if (drainStatus() >= PROCESSING_TO_IDLE) {
return;
}
if (evictionLock.tryLock()) {
try {
int drainStatus = drainStatus();
if (drainStatus >= PROCESSING_TO_IDLE) {
return;
}
lazySetDrainStatus(PROCESSING_TO_IDLE);
executor.execute(drainBuffersTask);
} catch (Throwable t) {
logger.log(Level.WARNING, "Exception thrown when submitting maintenance task", t);
maintenance(/* ignored */ null);
} finally {
evictionLock.unlock();
}
}
}
public void run() {
BoundedLocalCache, ?> cache = reference.get();
if (cache != null) {
cache.performCleanUp(/* ignored */ null);
}
}
void performCleanUp(@Nullable Runnable task) {
evictionLock.lock();
try {
maintenance(task);
} finally {
evictionLock.unlock();
}
if ((drainStatus() == REQUIRED) && (executor == ForkJoinPool.commonPool())) {
scheduleDrainBuffers();
}
}
void maintenance(@Nullable Runnable task) {
lazySetDrainStatus(PROCESSING_TO_IDLE);
try {
drainReadBuffer();
drainWriteBuffer();
if (task != null) {
task.run();
}
drainKeyReferences();
drainValueReferences();
expireEntries();
evictEntries();
climb();
} finally {
if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) {
lazySetDrainStatus(REQUIRED);
}
}
}
下面以 BoundedLocalCache#put 的方法为例阐述新增缓存的实现细节。主要步骤包括:
(1)创建node;
(2)新增缓存。
使用ConcurrentHashMap存储缓存信息。主要代码如下所示。
ConcurrentHashMap
public V put(K key, V value) {
return this.put(key, value, this.expiry(), true, false);
}
V put(K key, V value, Expiry expiry, boolean notifyWriter, boolean onlyIfAbsent) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
Node node = null;
long now = this.expirationTicker().read();
int newWeight = this.weigher.weigh(key, value);
Node prior;
Object oldValue;
int oldWeight;
boolean expired;
boolean mayUpdate;
boolean withinTolerance;
while(true) {
prior = (Node)this.data.get(this.nodeFactory.newLookupKey(key));
if (prior == null) {
if (node == null) {
// 1、创建node
node = this.nodeFactory.newNode(key, this.keyReferenceQueue(), value, this.valueReferenceQueue(), newWeight, now);
this.setVariableTime(node, this.expireAfterCreate(key, value, expiry, now));
}
if (notifyWriter && this.hasWriter()) {
prior = (Node)this.data.computeIfAbsent(node.getKeyReference(), (k) -> {
this.writer.write(key, value);
return node;
});
if (prior == node) {
this.afterWrite(new BoundedLocalCache.AddTask(node, newWeight));
return null;
}
} else {
// 2、新增缓存
prior = (Node)this.data.putIfAbsent(node.getKeyReference(), node);
if (prior == null) {
this.afterWrite(new BoundedLocalCache.AddTask(node, newWeight));
return null;
}
}
} else if (onlyIfAbsent) {
oldValue = prior.getValue();
if (oldValue != null && !this.hasExpired(prior, now)) {
if (!this.isComputingAsync(prior)) {
this.tryExpireAfterRead(prior, key, oldValue, this.expiry(), now);
this.setAccessTime(prior, now);
}
this.afterRead(prior, now, false);
return oldValue;
}
}
expired = false;
// ...
return expired ? null : oldValue;
}
(1)java缓存框架Caffeine详解