基于时间戳的驱逐进行缓存

时间:2014-12-18 21:41:51

标签: java spring caching guava

我的服务端点每分钟收到一个度量标准列表及其时间戳。如果指标通过某些条件,我们需要将它们存储在缓存中,以便以后可以访问它们。此服务的访问功能是 -

List<Metrics> GetAllInterestingMetrics5Mins();
List<Metrics> GetAllInterestingMetrics10Mins();
List<Metrics> GetAllInterestingMetrics30Mins();

我的解决方案是使用3个番石榴缓存,基于时间的驱逐设置为5,10和10。 15分钟。当有人调用上述函数之一时,我会从relvant缓存中返回所有指标。

这有两个问题 -

  1. 基于何时将值放入缓存(或访问,取决于设置)的驱逐的番石榴缓存开始时间。现在,可能会延迟指标,因此时间戳将早于将指标放入缓存的时间。
  2. 我不喜欢我必须创建3个缓存,当一个缓存有30分钟就足够时,它会增加内存占用和缓存处理的复杂性。
  3. 有没有办法在Guava或任何其他开箱即用的缓存解决方案中解决这两个问题?

3 个答案:

答案 0 :(得分:2)

像Guava和EHCache这样的缓存解决方案与您尝试实施的内容之间存在特殊差异。这些缓存的唯一目的是以与getter函数相同的方式工作。因此,缓存旨在通过其密钥检索单个元素并将其存储以供进一步使用;停止使用后将其驱逐出去。

E.g。

@Cacheable
public Object getter(String key){
...
}

这就是为什么从缓存中获取整组对象的感觉有点像强制缓存和驱逐策略与其最初目的不同。

您需要的是一个可以通过计时器功能一次性驱逐的集合,而不是Guava缓存(或其他缓存解决方案)。可悲的是,番石榴现在没有提供。您仍然需要应用程序提供的计时器功能,该功能将从缓存中删除所有现有元素。

所以,我的建议如下:

即使Guava有可能按照你想要的方式行事,你也会发现你没有使用让Guava真正有价值的功能,并且你“强迫”它表现得与众不同。所以我建议你忘记Guava的实现,并考虑使用例如AbstractMap类的特化,以及一个每隔N秒就会驱逐其内容的计时器函数。

通过这种方式,您可以将所有条目都放在一个缓存中,并且不必担心时间戳与条目添加到缓存之间的差异。

答案 1 :(得分:1)

您是否考虑使用类似Deque的内容?只需将指标放入队列中,当您想要检索最近N分钟的指标时,只需从最近添加的内容开始,然后取出所有内容,直到找到来自&gt;的指标。 N分钟前。您可以以类似的方式从另一端驱逐太旧的条目。 (我的问题不清楚Cache的关键/价值方面与您的问题有什么关系。)

答案 2 :(得分:1)

关于主题1:

只是旁注:请不要混淆到期和驱逐。到期意味着该条目可能不再由缓存返回,并且可能在指定的时间点或持续时间之后发生。驱逐是释放资源的操作,该条目从缓存中删除。到期后,驱逐可能同时或稍后发生。

所有常见的缓存产品都不支持精确的,也就是&#34;时间点&#34;,到期。我们经常在我们的应用程序中使用该用例,因此我花了一些精力与cache2k来支持这一点。

这是cache2k的蓝图:

static class MetricsEntry {

  long nextUpdate;
  List<Metrics> metrics;

}

static class MyEntryExpiryCalculator implements EntryExpiryCalculator<Integer, MetricsEntry> {
  @Override
  public long calculateExpiryTime(Integer _key, MetricsEntry _value, long _fetchTime, CacheEntry _oldEntry) {
    return _value.nextUpdate;
  }
}

Cache createTheCache() {
  Cache<Integer, MetricsEntry> cache =
    CacheBuilder.newCache(Integer.class, MetricsEntry.class)
      .sharpExpiry(true)
      .entryExpiryCalculator(new MyEntryExpiryCalculator())
      .source(new MySource())
      .build();
   return cache;
}

如果您在metrics对象中有时间引用,则可以使用它,并且可以省略其他条目类。 sharpExpiry(true)指示cache2k准确到期。如果你不这样做,到期时间可能会减少几毫秒,但访问时间会稍微快一点。

关于主题2:

直接的方法是使用间隔分钟作为缓存键。

这是一个缓存源(也就是缓存加载器),它严格返回前一个时间间隔的指标:

static class MySource implements CacheSource<Integer, MetricsEntry> {
  @Override
  public MetricsEntry get(Integer interval)  {
    MetricsEntry e = new MetricsEntry();
    boolean crossedIntervalEnd;
    do {
      long now = System.currentTimeMillis();
      long intervalMillis = interval * 1000 * 60;
      long startOfInterval = now % (intervalMillis);
      e.metrics = calculateMetrics(startOfInterval, interval);
      e.nextUpdate = startOfInterval + intervalMillis;
      now = System.currentTimeMillis();
      crossedIntervalEnd = now >= e.nextUpdate;
    } while (crossedIntervalEnd);
    return e;
  }
}

如果您按照10:07进行请求,那将返回10:00-10:05的指标。

如果您只是想立即计算过去时间间隔的指标,那么它就更简单了:

static class MySource implements CacheSource<Integer, MetricsEntry> {
  @Override
  public MetricsEntry get(Integer interval)  {
    MetricsEntry e = new MetricsEntry();
    long intervalMillis = interval * 1000 * 60;
    long startOfInterval = System.currentTimeMillis();
    e.metrics = calculateMetrics(startOfInterval, interval);
    e.nextUpdate = startOfInterval + intervalMillis;
    return e;
  }
}

使用缓存源优于put()。 cache2k是阻塞的,因此如果一个指标有多个请求,则只启动一个指标计算。

如果您不需要精确到期毫秒,您也可以使用其他缓存。您需要做的是存储计算缓存值中的指标所需的时间,然后相应地更正到期时间。

有一个好的!