31

我在我的项目中使用 Guava 的 LoadingCache 来处理线程{安全,友好}缓存加载,它工作得非常好。但是,有一个限制。

当前定义缓存的代码如下所示:

cache = CacheBuilder.newBuilder().maximumSize(100L).build(new CacheLoader<K, V>()
{
    // load() method implemented here
}

我没有指定到期时间。

问题是根据键的值,一些关联的值可能会过期,而其他的可能不会。并且CacheLoader不考虑这一点,如果您指定到期时间,则它适用于每个条目。

你将如何解决这个问题?

4

7 回答 7

34

另一种选择是ExpiringMap,它支持变量条目过期:

Map<String, String> map = ExpiringMap.builder().variableExpiration().build();
map.put("foo", "bar", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES);
map.put("baz", "pez", ExpirationPolicy.CREATED, 10, TimeUnit.MINUTES);
于 2014-12-16T01:42:55.713 回答
13

我建议您将过期时间直接包含在您的条目类中,如果它在您从缓存中获取后立即过期,则手动将其从缓存中逐出:

MyItem item = cache.getIfPresent(key);
if (item != null && item.isExpired()) {
    cache.invalidate(key);
    item = cache.get(key);
    // or use cache.put if you load it externally
}

作为替代方案,我建议您检查支持每个元素过期策略的 EhCache 库。

于 2012-12-20T20:15:59.043 回答
7

LoadingCache提供了一些常用的过期策略,但当这些策略无法满足您的需求时,您需要自行推出。

只需添加一个DelayQueue。每当您将某些内容添加到缓存中时,Delayed请在该队列中添加一个并具有适当的到期时间。该Delayed对象应该有一个(弱?)对键的引用。

最后一个要素是您需要定期轮询此队列,以查看是否有某些内容过期并且必须被驱逐。不一定要添加线程来执行此操作,您可以捎带任何正在访问LoadingCache. 就在访问缓存之前,例如:

private void drainCache() {
  MyDelayed expired;
  while ((expired = delayedQueue.poll()) != null) {
    K key = expired.getReference();
    if (key != null) { // this only in case if you hold the key in a weak reference
      loadingCache.invalidate(key);
    }
  }
}

..
V lookup(K key) {
  drainCache();
  return loadingCache.getUnchecked(key);
}
于 2013-01-14T01:23:12.930 回答
0

我想您可以使用显式无效来准确定义应该驱逐哪些条目,但这可能不是您想要的。

不过,您可以为条目赋予不同的权重。它并不完美,但您可以引导缓存驱逐不太重要的条目。参见Weighter,权重为 0 的条目不会被基于大小的驱逐驱逐。

于 2012-12-20T20:04:20.733 回答
0

如果条目很大并且您需要节省内存,我认为没有一个很好的解决方案,但请记住这些黑客:

  • 使用PriorityQueue按到期时间排序的手动删除条目。如果你想确保没有过期的条目被使用,你需要将它与 hoaz 的解决方案结合起来;队列只防止无用的条目占用内存。

  • 您写了“一些关联的值可能会过期,而其他的可能不会”,这表明所有过期条目的过期延迟都是相同的。这将允许使用更简单和更快的Queue(例如ArrayDeque,而不是PriorityQueue)。

  • 如果过期延迟相当大,您可以让所有条目过期并重新插入那些应该永远存在的RemovalListener. 这可能会以两种方式失败: 1. 在此期间您可能会错过。2. 删除和重新插入可能会花费大量 CPU 时间。

于 2012-12-20T21:24:26.283 回答
0

提到的咖啡因库提供了这个功能。你只需要提供你自己的 Expiry 实现。到期时间可能取决于键、值或您决定的任何内容,例如:

    .expireAfter(new Expiry<CacheKey, CacheValue>() {
                        @Override
                        public long expireAfterCreate(@NonNull CacheKey key, @NonNull CacheValue value, long currentTime) {
                            return calculateBasedOnKey(key);
                        }
                        @Override
                        public long expireAfterUpdate(@NonNull CacheKey key, @NonNull CacheValue value, long currentTime, @NonNegative long currentDuration) {
                            return calculateBasedOnValue(value);
                        }    
                        @Override
                        public long expireAfterRead(@NonNull CacheKey key, @NonNull Long CacheValue, long currentTime, @NonNegative long currentDuration) {
                            return currentDuration; //not modifying the expiration time
                        }
于 2022-02-05T08:24:43.610 回答
-1

您可以使用受 Guava 启发的咖啡因库。这是来自 github repo 的示例用法

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterAccess(5, TimeUnit.MINUTES)

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterCreate(5, TimeUnit.MINUTES)

https://github.com/ben-manes/caffeine

于 2019-04-09T04:59:30.350 回答