Cache 或 MultiMap 用于基于天的缓存到期?
Posted
技术标签:
【中文标题】Cache 或 MultiMap 用于基于天的缓存到期?【英文标题】:Cache or MultiMap for day-based cache expiration? 【发布时间】:2012-11-26 12:42:37 【问题描述】:背景:我正在为订购系统开发分析系统。每天大约有 100,000 个订单,并且需要在最后 N(例如 100)天的几个月内运行分析。相关数据适合内存。 N 天后,所有订单都从内存缓存中逐出,过去一整天都被逐出。可以创建或更新订单。
传统方法会使用ConcurrentHashMap<Date, Queue<Order>>
。每天,表示过去 N 天以上日期的键的值将被删除。但是,当然,使用 Guava 的全部目的是避免这种情况。编辑:将Map
更改为ConcurrentHashMap
,请参阅问题的结尾以了解基本原理。
使用 Guava 集合,MultiMap <Date, Order>
会更简单。驱逐类似,明确实施。
虽然Cache
实现看起来很吸引人(毕竟,我正在实现缓存),但我不确定驱逐选项。驱逐每天只发生一次,最好从缓存外部启动,我不希望缓存必须检查订单的年龄。我什至不确定缓存是否会使用 MultiMap,我认为在这种情况下它是一个合适的数据结构。
因此,我的问题是:是否可以使用使用并公开 MultiMap 语义并允许从外部控制驱逐的缓存,特别是使用我需要的规则(“删除所有早于 N 天的订单” ) ?
作为一个重要的澄清,我对LoadingCache
不感兴趣,但我确实需要批量加载(如果需要重新启动应用程序,则必须从数据库中填充缓存,最后 N 天订单)。
编辑:忘记提及地图需要并发,因为订单进来时,它们会根据之前针对同一客户或位置等的订单进行实时评估。
EDIT2:刚刚偶然发现Guava issue 135。 MultiMap 好像不是并发的。
【问题讨论】:
参见Guava issue #142(Cache
是ConcurrentMap
的继任者,由MapMaker
制作)和this question。
关于edit#2:您可以使用Multimaps#synchronizedMultimap
获得由指定的多图支持的同步(线程安全)多图。
@Xaerxess 谢谢,我得测试一下它的性能;我有些担心它不会像 ConcurrentHashMap 那样好,在这种情况下,我将不得不重新使用 JDK 类(即问题中的方法 #1)。
【参考方案1】:
我不会在这里使用Cache
或Multimap
。虽然我喜欢并使用它们,但这里并没有什么好处。
Cache
的功能在这里并没有真正得到使用。
您正在考虑使用ConcurrentHashMap<Date, Queue<Order>>
,它在某种意义上比Multimap<Date, Order>
更强大。
我会使用Cache
,如果我考虑不同的驱逐标准并且我觉得随时丢失它的任何条目1 很好。
您可能会发现您需要ConcurrentMap<Date, Dequeue<Order>>
或ConcurrentMap<Date, YouOwnQueueFastSearchList<Order>>
或其他。这可能由Multimap
以某种方式管理,但恕我直言,它变得更复杂而不是更简单。
我会问自己“在这里使用Cache
或Multimap
可以获得什么?”。对我来说,它看起来就像普通的旧 ConcurrentMap
提供了你需要的一切。
1 我绝不是在暗示 Guava 会发生这种情况。相反,没有驱逐原因(容量,到期,......)它就像ConcurrentMap
一样工作。只是你描述的感觉更像Map
,而不是Cache
。
【讨论】:
我认为你是对的;早些时候我看到这个评论“注意:如果你不需要缓存的特性,ConcurrentHashMap 更节省内存——但是用任何旧的 ConcurrentMap 复制大多数缓存特性是极其困难或不可能的。”在code.google.com/p/guava-libraries/wiki/CachesExplained 中,虽然 Cache 可以返回 ConcurrentMap,但我认为不值得使用它。【参考方案2】:恕我直言,最简单的做法是在订单记录中包含订单日期。 (我希望它已经是一个字段)由于您每天只需要清理一次缓存,因此它不必非常高效,只需合理及时即可。
例如
public class Main
static class Order
final long time;
Order(long time)
this.time = time;
public long getTime()
return time;
final Map<String, Order> orders = new LinkedHashMap<String, Order>();
public void expireOrdersOlderThan(long dateTime)
for (Iterator<Order> iter = orders.values().iterator(); iter.hasNext(); )
if (iter.next().getTime() < dateTime)
iter.remove();
private void generateOrders()
for (int i = 0; i < 120000; i++)
orders.put("order-" + i, new Order(i));
public static void main(String... args)
for (int t = 0; t < 3; t++)
Main m = new Main();
m.generateOrders();
long start = System.nanoTime();
for (int i = 0; i < 20; i++)
m.expireOrdersOlderThan(i * 1000);
long time = System.nanoTime() - start;
System.out.printf("Took an average of %.3f ms to expire 1%% of entries%n", time / 20 / 1e6);
打印
Took an average of 9.164 ms to expire 1% of entries
Took an average of 8.345 ms to expire 1% of entries
Took an average of 7.812 ms to expire 1% of entries
对于 100,000 个订单,我预计这需要大约 10 毫秒,这在半夜的安静时段不会发生太多。
顺便说一句:如果您的 OrderId 按时间排序,您可以提高效率。 ;)
【讨论】:
【参考方案3】:您是否考虑过使用某种排序列表?它将允许您提取条目,直到您找到一个足够新鲜的条目。当然,这假设这是您的主要功能。如果您最需要的是使用哈希图进行 O(1) 访问,那么我的回答不适用。
【讨论】:
订单日期是关键,因此在方法 #1 中,整个订单集合(存储在队列中)被驱逐。但问题更多是关于 #2 与 #3。以上是关于Cache 或 MultiMap 用于基于天的缓存到期?的主要内容,如果未能解决你的问题,请参考以下文章