设计具有可变条目到期的Guava LoadingCache

时间:2012-12-20 19:45:41

标签: java multithreading guava

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

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

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

我没有指定到期时间。

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

你会如何解决这个问题?

6 个答案:

答案 0 :(得分:27)

另一个替代方案是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);

答案 1 :(得分:11)

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

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库。

答案 2 :(得分:6)

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);
}

答案 3 :(得分:0)

我猜你可以使用显式的invalidate来确切地定义哪些条目应该被驱逐,但这可能不是你想要的。

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

答案 4 :(得分:0)

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

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

  • 您写道“某些关联值可能会过期而其他值可能不会过期”,这表明所有过期条目的过期延迟都是相同的。这样可以使用更简单,更快捷Queue(例如ArrayDeque代替PriorityQueue)。

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

答案 5 :(得分:0)

您可以使用受番石榴启发的咖啡因库。 这是来自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