问题描述
使用 Caffeine 2.8.1 和 Java 8。
我已经创建了 LoadingCache<String,Boolean>
。我正在使用 cache.putAll(getAllkeyvalues())
加载缓存,其中 getAllkeyvalues()
返回 Map<String,Boolean>
。
我指定了一个 CacheLoader 来通过调用 keyExistsOnServer(key)
方法来计算值,该方法调用外部服务来获取值。
我已指定自定义过期策略,如果在创建/更新缓存中的条目时将该值设置为 false
,我想在 10 分钟内从缓存中逐出该条目。如果值为 Long.MAX_VALUE
,则将其保持为 true
。
private RemovalListener<String,Boolean> removeListener =
(key,value,cause) ->
logger.info(
"Entry for Key={} with value={} was removed ({}) from cache",key,cause);
private LoadingCache<String,Boolean> cache =
caffeine.newBuilder()
.expireAfter(
new Expiry<String,Boolean>() {
private static final long EXPIRATION_TEN_MINS_IN_NANOSECONDS = 600000000000L;
@Override
public long expireAfterCreate(String key,Boolean value,long currentTime) {
// If value is false that means key does not exist,so set expiration to 10 mins
return value ? Long.MAX_VALUE : EXPIRATION_TEN_MINS_IN_NANOSECONDS;
}
@Override
public long expireAfterUpdate(
String key,long currentTime,long currentDuration) {
// If value is changed from true to false then set expiration to 10 mins
return value ? Long.MAX_VALUE : EXPIRATION_TEN_MINS_IN_NANOSECONDS;
}
@Override
public long expireAfterRead(
String key,long currentDuration) {
// Don't modify expiration time after read
return currentDuration;
}
})
.recordStats()
.removalListener(removeListener)
.build(key -> keyExistsOnServer(key));
问题#1:根据我想要实现的目标,我的到期政策看起来是否正确?
问题#2:
我没有看到根据驱逐政策调用 RemovalListener
。这可能是由于本 github issue 中所述的清理任务的累积。
但是,我的代码的正确性取决于这样一个事实,即一旦条目的过期时间(false
值的情况下为 10 分钟)已经过去,并且如果我们调用 cache.get(key)
,那么它不应返回缓存中的过期值,而是调用 CacheLoader
即 keyExistsOnServer(key) 方法来获取值。有人可以断言这就是它的行为方式吗?
以下是@Louis Wasserman 在 github 问题上的说明,但我不清楚这是否说明了这一点:
实际上发生的事情是get调用本身发现该条目已过期并将其添加到要清理的条目队列中
问题#3:如果在调用 {{1} 时 RuntimeException
是 keyExistsOnServer(key) 方法抛出 CacheLoader
会发生什么}}?
问题#4:cache.get(key)
是否包含已过期但由于清理累积而未被驱逐的条目?
问题#5:当我在 cache.asMap()
方法中记录 currentDuration
时,它似乎与经过的时间不一致。即如果它被设置为 600000000000(10 分钟)并更新为 expireAfterRead()
我希望在 5 分钟后它应该是 300000000000 但它不是。为什么会这样?
解决方法
问题#1:根据我想要实现的目标,我的到期政策看起来是否正确?
是的,这看起来不错。您可以将常量设为 Duration
或使用 TimeUnit
进行计算,只是为了让它看起来更漂亮。
问题#2:根据驱逐政策,我没有看到 RemovalListener 被调用。
当缓存上发生足够多的活动时,就会发生这种情况。您可以指定一个 Scheduler
,它会根据下一个条目的过期时间来唤醒并为您调用 cache.cleanUp
。对于 Java 9+ 用户,Java 提供了一个内置的调度线程,您可以通过在构建缓存时添加 Caffeine.scheduler(Scheduler.systemScheduler)
来利用该线程。
一旦一个条目的过期时间已经过去,如果我们调用 cache.get(key) 那么它就不应该从缓存中返回过期的值,而是调用 CacheLoader。
正确。缓存将在查找时验证条目,如果过期,则重新加载它。它永远不会返回过期的条目。
问题#3:如果调用 cache.get(key) 方法时 CacheLoader 抛出 RuntimeException 会发生什么?
映射不会建立,异常会传播给调用者。对 get(key)
的下一次调用将重新尝试,这可能会再次失败。您可以选择如何对此保持弹性。通常不是这样是一个很好的答案,或者有时缓存它失败是一个更好的答案。
问题#4:cache.asMap() 是否包含已过期但由于清理累积而未被驱逐的条目?
缓存将保存条目但禁止它们可见,例如通过查找或迭代。唯一的外部指示是 size()
,因为在删除过期条目之前,该计数器不会更新。
问题#5:当我在 expireAfterRead() 方法中记录 currentDuration 时,它似乎与经过的时间不一致。即如果它被设置为 600000000000(10 分钟)并更新为 false 我期望在 5 分钟后它应该是 300000000000 但它不是。为什么会这样?
请用单元测试打开一个问题,我们可以诊断。