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(CacheConcurrentMap 的继任者,由MapMaker 制作)和this question。 关于edit#2:您可以使用Multimaps#synchronizedMultimap 获得由指定的多图支持的同步(线程安全)多图 @Xaerxess 谢谢,我得测试一下它的性能;我有些担心它不会像 ConcurrentHashMap 那样好,在这种情况下,我将不得不重新使用 JDK 类(即问题中的方法 #1)。 【参考方案1】:

我不会在这里使用CacheMultimap。虽然我喜欢并使用它们,但这里并没有什么好处。

您想手动逐出您的条目,因此Cache 的功能在这里并没有真正得到使用。 您正在考虑使用ConcurrentHashMap<Date, Queue<Order>>,它在某种意义上比Multimap<Date, Order> 更强大。

我会使用Cache,如果我考虑不同的驱逐标准并且我觉得随时丢失它的任何条目1 很好。

您可能会发现您需要ConcurrentMap<Date, Dequeue<Order>>ConcurrentMap<Date, YouOwnQueueFastSearchList<Order>> 或其他。这可能由Multimap 以某种方式管理,但恕我直言,它变得更复杂而不是更简单。

我会问自己“在这里使用CacheMultimap 可以获得什么?”。对我来说,它看起来就像普通的旧 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 用于基于天的缓存到期?的主要内容,如果未能解决你的问题,请参考以下文章

Cache 简介

Django2.2 Cache缓存的设计以及几种方式的 多级或单级缓存处理

Guava Cache本地缓存

什么是Java缓存技术Cache

PHP批量清理MIP-cache缓存

mysql 缓存