【问题标题】:How to reliably drop records from Guava LoadingCache?如何可靠地从 Guava LoadingCache 中删除记录?
【发布时间】:2016-12-13 04:05:49
【问题描述】:

我正在使用 Guava LoadingCache 向其中填充一些数据,并且我想每 1 分钟从该 LoadingCache 中删除所有条目。

public class MetricHolder {
  private final ExecutorService executor = Executors.newFixedThreadPool(2);
  private final LoadingCache<String, AtomicLongMap<String>> clientIdMetricCounterCache =
      CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
          .removalListener(RemovalListeners.asynchronous(new SendToDatabase(), executor))
          .build(new CacheLoader<String, AtomicLongMap<String>>() {
            @Override
            public AtomicLongMap<String> load(String key) throws Exception {
              return AtomicLongMap.create();
            }
          });

  private static class Holder {
    private static final MetricHolder INSTANCE = new MetricHolder();
  }

  public static MetricHolder getInstance() {
    return Holder.INSTANCE;
  }

  private MetricHolder() {}

  public void increment(String clientId, String name) throws ExecutionException {
    clientIdMetricCounterCache.get(clientId).incrementAndGet(name);
  }

  public LoadingCache<String, AtomicLongMap<String>> getClientIdMetricCounterCache() {
    return clientIdMetricCounterCache;
  }

  private static class SendToDatabase implements RemovalListener<String, AtomicLongMap<String>> {
    @Override
    public void onRemoval(RemovalNotification<String, AtomicLongMap<String>> notification) {
      String key = notification.getKey();
      AtomicLongMap<String> value = notification.getValue();
      System.out.println(key);
      System.out.println(value);
      // sending these key/value to some other system

    }
  }
}

我以多线程方式从代码中的许多不同位置调用increment 方法。因此,在 1 分钟内,它将在 clientIdMetricCounterCache 中填充大量指标。现在我想在每 1 分钟后可靠删除所有这些指标,并将所有这些指标发送到数据库。

在我的情况下,有时写入increment 方法可能非常慢,但我仍然想每 1 分钟删除所有这些条目,并且我根本没有在这个缓存上进行任何读取,只是写入它然后删除这些记录通过发送到其他系统。下面是我在番石榴看到的wiki

使用 CacheBuilder 构建的缓存不执行清理和逐出值 “自动”或值到期后立即,或任何 排序。相反,它在期间执行少量维护 写操作,或者在偶尔的读操作期间,如果写是 很少见。

那么expireAfterWrite 是如何工作的?它是否像调度程序一样工作,每 1 分钟运行一次并删除 clientIdMetricCounterCache 中的所有条目,然后它会在 1 分钟后再次唤醒并从同一个缓存中删除所有条目并继续这样?阅读 wiki 后,我怀​​疑它是否可以这样工作。如果没有,那么我如何可靠地每 1 分钟删除一次这些记录并发送到其他系统,因为我的写入可能在一段时间内很少见?

看起来我可能必须使用Guava TimeLimiter 接口和SimpleTimeLimiter 或者可能是ScheduledExecutorService 才能可靠地使呼叫超时然后丢弃条目?如果是,任何人都可以提供一个示例,这在我当前的示例中将如何工作?

【问题讨论】:

  • 我看不出TimeLimiter 与任何事情有什么关系,但您引用的文档清楚地回答了这个问题:创建一个定期调用Cache.cleanUp() 的后台线程。
  • 值得注意的是,由于您是在 修改 缓存值而不是替换它们,如果另一个线程引用了AtomicLongMap 你正在尝试清理。
  • @shmosel 是的,我在 wiki 上看到了。我的困惑是我应该在后台线程中做什么?只需定期致电Cache.cleanUp() 意思是clientIdMetricCounterCache.cleanUp()?如果你能提供一个例子,那么这将消除我的困惑。
  • @shmosel 您在哪里看到我正在修改这些值而不是替换它们?
  • 每次拨打incrementAndGet()

标签: java multithreading caching guava google-guava-cache


【解决方案1】:

在我看来,您似乎在滥用缓存,而 Map 会这样做。你没有使用过期,没有大小限制,没有缓存,你只是在收集统计数据。

关于您正在使用的唯一功能是加载方面,这并不值得。

我建议改用AtomicReference&lt;ConcurrentHashMap&lt;String, AtomicLongMap&gt;&gt;

  • 更新时,您会通过AtomicReference::get 获取当前分钟的版本。
  • 使用clientId,您可以在ConcurrentHashMap 中查找AtomicLongMap,如果找不到则创建一个新的(在Java 7 上使用putIfAbsent,在Java 8 上使用computeIfAbsent)。
  • 使用name,您可以更新AtomicLongMap,就像您发布的那样。
  • 每分钟一次,您通过AtomicReference::getAndSet 替换所有内容。

通过替换,您可以确保您的统计不会干扰,但是,您应该在getAndSet 之后稍等片刻,因为可能有线程刚刚获得引用并即将写入。

它会比原来的方法产生更多的垃圾,但是所有的垃圾都是短暂的,所以你实际上可能会让 GC 更快乐。

它很简单,不需要深入了解库或其实现细节。


我猜,volatile 代替 AtomicReference 也可以。

【讨论】:

  • 我必须在这里使用AtomicReferencevolatile 吗?如果我只是有这个并从多个线程填充到这个映射中,那么会有什么问题吗? ConcurrentMap&lt;String, AtomicLongMap&lt;String&gt;&gt; clientIdMetricCounterCache = Maps .newConcurrentMap();
  • @david 我的想法是每分钟用一张新地图替换地图。这个想法与使用地图而不是缓存的部分正交。所以可以肯定的是,它可以在不更换地图的情况下工作,它可能会使周期性操作更加复杂(因为它可能需要删除条目,而正常操作会添加和使用它们)。
猜你喜欢
  • 2021-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-27
  • 2021-07-27
  • 2011-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多