可靠地迫使番石榴地图被驱逐

Pau*_*ora 19 java caching guava

编辑:我重新组织了这个问题,以反映自那以后可用的新信息.

这个问题是基于对Viliam关于Guava Maps使用懒惰驱逐的问题的回答:Guava地图中的驱逐懒惰

请首先阅读这个问题及其回答,但基本上结论是Guava地图不会异步计算并强制执行驱逐.给出以下地图:

ConcurrentMap<String, MyObject> cache = new MapMaker()
        .expireAfterAccess(10, TimeUnit.MINUTES)
        .makeMap();
Run Code Online (Sandbox Code Playgroud)

在访问条目后十分钟过后,在再次"触摸"地图之前,它仍然不会被驱逐.已知的方法可以做到这包括平时的存取- get()put()containsKey().

问题的第一部分[已解决]:其他调用会导致地图被"触摸"?具体来说,有谁知道是否size()属于这一类?

想知道这一点的原因是我已经实现了一个计划任务,偶尔轻推我用于缓存的Guava地图,使用这个简单的方法:

public static void nudgeEviction() {
    cache.containsKey("");
}
Run Code Online (Sandbox Code Playgroud)

但是,我还使用cache.size()以编程方式报告地图中包含的对象数量,以确认此策略是否有效.但我无法看到这些报道的差异,现在我想知道是否size()也会导致驱逐.

答:所以马克指出,在第9版,驱逐仅被调用get(),put()replace()方法,这可以解释为什么我没有看到一个效果containsKey().这显然会随着下一版本的番石榴而改变,该版本即将发布,但不幸的是我的项目发布时间越早.

这让我陷入了一个有趣的困境.通常我仍然可以通过调用来触摸地图get(""),但我实际上正在使用计算地图:

ConcurrentMap<String, MyObject> cache = new MapMaker()
        .expireAfterAccess(10, TimeUnit.MINUTES)
        .makeComputingMap(loadFunction);
Run Code Online (Sandbox Code Playgroud)

其中loadFunction加载MyObject对应于数据库中的关键.它开始看起来像我没有简单的方法强迫驱逐直到r10.但是,即使能够可靠地强制驱逐,我的问题的第二部分仍然存在疑问:

我的问题的第二部分[解决]:在反应的反应之一链接的问题,并触摸地图可靠驱逐所有过期项?在链接的答案中,Niraj Tolia另有说明,称驱逐可能只是分批处理,这意味着可能需要多次调用触摸地图以确保所有过期的对象被驱逐.他没有详细说明,但这似乎与基于并发级别的地图被拆分成相关.假设我使用r10,其中a containsKey("")确实调用驱逐,那么这将是整个地图,还是只针对其中一个段?

答: maaartinus已经解决了这部分问题:

请注意,containsKey只运行其他读取方法postReadCleanup,除了每次第64次调用之外什么都不做(参见DRAIN_THRESHOLD).此外,看起来所有清理方法仅适用于单个Segment.

所以看起来调用containsKey("")不是一个可行的修复,即使在r10.这使我的问题减少到标题:我如何可靠地强制驱逐?

注意:我的Web应用程序明显受此问题影响的部分原因是,当我实现缓存时,我决定使用多个映射 - 每个映射一个数据对象.所以有了这个问题,就有可能执行一个代码区域,导致一堆Foo对象被缓存,然后Foo长时间不再触摸缓存,所以它不会驱逐任何东西.与此同时Bar,Baz正在从其他代码区域缓存对象,并且正在使用内存.我在这些地图上设置了最大尺寸,但这最好是一个脆弱的保护措施(我假设其影响是立竿见影的 - 仍然需要确认这一点).

更新1:感谢Darren将相关问题联系起来 - 他们现在有了我的投票.所以看起来分辨率正在酝酿中,但似乎不太可能出现在r10中.与此同时,我的问题仍然存在.

更新2:此时我只是在等待一名番石榴队成员就hack maaartinus提出反馈并将其放在一起(参见下面的答案).

最后更新:收到反馈!

小智 7

我刚刚将方法添加Cache.cleanUp()到Guava中.从迁移MapMakerCacheBuilder您可以使用它来强制驱逐.


Mar*_*ard 6

我想知道你在问题的第一部分中描述的相同问题.从我可以从看为番石榴的源代码告诉CustomConcurrentHashMap(第9版),看来条目的驱逐get(),put()replace()方法.该containsKey()方法似乎不会调用驱逐.我不是百分百肯定,因为我快速通过了代码.

更新:

我还在Guava的git存储库中找到了一个更新版本的CustomConcurrentHashmap,看起来containsKey()已经更新以调用驱逐.

版本9和我刚发现的最新版本在调用时都不会调用驱逐size().

更新2:

我最近注意到Guava r10(尚未发布)有一个名为CacheBuilder的新类.基本上这个类是一个分叉版本,MapMaker但考虑到缓存.文档表明它将支持您正在寻找的一些驱逐要求.

我查看了r10版本的CustomConcurrentHashMap中的更新代码,发现了看起来像预定的地图清理器.不幸的是,此代码似乎尚未完成,但r10每天看起来越来越有希望.


maa*_*nus 5

请注意,containsKey只运行其他读取方法postReadCleanup,除了每次第64次调用之外什么都不做(参见DRAIN_THRESHOLD).此外,看起来所有清理方法仅适用于单个Segment.

强制驱逐的最简单方法似乎是将一些虚拟对象放入每个段中.为了实现这一点,你需要进行分析CustomConcurrentHashMap.hash(Object),这肯定不是一个好主意,因为这种方法可能随时改变.此外,根据密钥类,可能很难找到具有hashCode的密钥,以确保它落在给定的段中.

您可以使用读取,但每段需要重复64次.在这里,很容易找到具有适当hashCode的密钥,因为这里允许任何对象作为参数.

也许你可以CustomConcurrentHashMap反而入侵源代码,它可能是微不足道的

public void runCleanup() {
    final Segment<K, V>[] segments = this.segments;
    for (int i = 0; i < segments.length; ++i) {
        segments[i].runCleanup();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是如果没有很多测试和/或番石榴团队成员的确定,我就不会这样做.