如何从Guava LoadingCache中可靠地删除记录?

时间:2016-12-13 04:05:49

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

我正在使用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分钟后再次唤醒并从同一缓存中删除所有条目并继续这样做?阅读维基后,我怀疑它是那样的。如果没有,那么我怎样才能每隔1分钟可靠地丢弃这些记录并发送到其他系统,因为我的写入在一段时间内很少见?

看起来我可能必须使用Guava TimeLimiter界面和SimpleTimeLimiter或可能ScheduledExecutorService来可靠地超时,然后删除条目?如果是的话,任何人都可以提供一个示例,在当前示例中这将如何工作?

1 个答案:

答案 0 :(得分:2)

对我来说,看起来你正在滥用缓存,而地图就是这样做的。您没有使用过期,没有大小限制,没有缓存,您只是收集统计信息。

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

我建议改为使用AtomicReference<ConcurrentHashMap<String, AtomicLongMap>>

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

通过替换,您可以确定您的统计信息不会干扰,但是,您应该在volatile之后稍等一下,因为可能有线程刚刚获得参考并且即将写入。

它会产生比原始方法更多的垃圾,但所有垃圾都会短暂存在,所以你可能会让GC更加快乐。

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

我猜,AtomicReference代替{{1}}也可以。