缓存无效-是否有通用解决方案?


118

“计算机科学中只有两个难题:缓存失效和命名。”

菲尔·卡尔顿

是否有使缓存无效的一般解决方案或方法;知道什么时候条目是陈旧的,所以可以保证您总是能获得最新数据?

例如,考虑一个getData()从文件获取数据的函数。它根据文件的上次修改时间对其进行缓存,并在每次调用时对其进行检查。
然后添加第二个函数transformData(),该函数转换数据,并缓存其结果,以备下次调用该函数时使用。它不知道文件-如何添加依赖项,如果文件被更改,此缓存将变为无效?

您可以在getData()每次调用时transformData()进行调用,并将其与用于构建缓存的值进行比较,但这最终可能会非常昂贵。


6
我相信他与编写X Windows有关
Greg

1
我认为标题最好是“缓存无效-是否有通用解决方案?” 因为它指的是特定类别的缓存问题。
RBarryYoung

71
不,他对计算机科学知识不多。我确信他参与创建OpenGL,X11和SSLv3的工作使他太忙了,无法对其进行深入研究。:-)
蒂姆·莱瑟

80
在计算机科学中只有两个难题:缓存失效。命名事物。和一对一的错误。
达格

8
我曾经听过这样的话"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
乔纳森·莱因哈特

Answers:


55

您所谈论的是生命周期依赖关系链,一件事依赖于另一件事,可以在其控制范围之外进行修改。

如果你有一个幂函数,从abc那里,如果ab相同,则c是相同的,但检查的成本b是高的,那么你可以:

  1. 接受您有时会使用过时的信息进行操作,并且不要总是检查 b
  2. 尽最大可能使检查b尽快

你不能吃蛋糕也不能吃...

如果您可以a在顶部放置一个额外的缓存,那么这将对最初的问题产生一点影响。如果选择1,那么您将拥有自己的一切自由,因此可以缓存更多,但必须记住要考虑的缓存值的有效性b。如果选择2,则您仍必须b每次都检查一次,但可以退回缓存以检查a是否b签出。

如果您分层缓存,则必须考虑是否由于综合行为而违反了系统的“规则”。

如果您知道a始终有效b,那么您可以这样安排您的缓存(伪代码):

private map<b,map<a,c>> cache // 
private func realFunction    // (a,b) -> c

get(a, b) 
{
    c result;
    map<a,c> endCache;
    if (cache[b] expired or not present)
    {
        remove all b -> * entries in cache;   
        endCache = new map<a,c>();      
        add to cache b -> endCache;
    }
    else
    {
        endCache = cache[b];     
    }
    if (endCache[a] not present)     // important line
    {
        result = realFunction(a,b); 
        endCache[a] = result;
    }
    else   
    {
        result = endCache[a];
    }
    return result;
}

x只要在每个阶段新添加的输入的有效性与:和:的ab关系匹配,显然连续的分层(say )都是微不足道的。xbxa

但是,您很有可能获得三个输入,其有效性完全独立(或循环),因此不可能分层。这意味着标记为//重要的行必须更改为

如果(endCache [a] 过期或不存在)


3
或者,如果检查b的成本很高,则可以使用pubsub,这样当b更改时,它会通知c。观察者模式是常见的。
user1031420 2014年

15

缓存无效化的问题是,东西在我们不知道的情况下发生了变化。因此,在某些情况下,如果有其他事情确实知道并可以通知我们,则可能有解决方案。在给定的示例中,getData函数可以挂接到文件系统,该文件系统确实知道对文件的所有更改,而不管是什么进程更改了文件,并且该组件又可以通知要转换数据的组件。

我认为没有任何一般的魔术方法可以解决问题。但是在许多实际情况下,很有可能将基于“轮询”的方法转换为基于“中断”的方法,这可以使问题简单化。


3

如果每次进行转换时都要使用getData(),那么您就消除了缓存的全部好处。

以您的示例为例,似乎一种解决方案是在生成转换后的数据时还存储生成数据的文件名和文件的上次修改时间(您已经将其存储在getData返回的任何数据结构中( ),因此您只需将该记录复制到transformData()返回的数据结构中,然后在再次调用transformData()时,检查文件的最后修改时间。


3

恕我直言,功能反应式编程(FRP)在某种意义上是解决缓存失效的一种通用方法。

原因如下:FRP术语中的过时数据称为故障。FRP的目标之一是确保无故障。

在此“ FRP的本质”演讲和此SO答复中将更详细地说明FRP

对话中Cells表示缓存的对象/实体,Cell如果a 的依赖关系被刷新,则a被刷新。

FRP隐藏与依赖关系图关联的管道代码,并确保没有陈旧Cell的代码。


我可以想到的另一种方式(与FRP不同)是将(类型的b)计算值包装到某种类型的writer Monad中Writer (Set (uuid)) b,其中Set (uuid)(Haskell表示法)包含计算值b所依赖的可变值的所有标识符。因此,uuid某种独特的标识符可以标识计算b值所依赖的可变值/变量(例如数据库中的一行)。

将此思想与在这种编写器Monad上运行的组合器结合起来,如果仅使用这些组合器来计算新的组合器,则可能会导致某种通用的缓存失效解决方案b。这样的组合器(例如的特殊版本filter)将Writer monads和(uuid, a)-s作为输入,其中a的可变数据/变量由标识uuid

因此,每次更改类型的计算值所依赖的“原始”数据(uuid, a)(例如,要从b中计算数据库的规范化数据)时,如果您b对包含计算值所依赖的b任何值a进行突变,就可以使其中包含的缓存无效b,因为根据Set (uuid)Writer Monad中的,您可以确定何时发生这种情况。

因此,每当您使用给定变量进行某些uuid突变时,您都将这种突变广播到所有的高速缓存中,并且它们会使b依赖于用said标识的可变值的值无效,uuid因为b包裹在其中的Writer monad 可以判断出该变量是否b取决于said uuid或不。

当然,只有当您阅读的次数多于写作时,这才有回报。


第三种实用方法是在数据库中使用实例化视图,并将其用作缓存。AFAIK他们还旨在解决失效问题。当然,这限制了将可变数据连接到派生数据的操作。


2

我现在正在研究一种基于PostSharp备注功能的方法。我已经把它交给了我的导师,他也同意这是一种与内容无关的缓存的良好实现。

每个函数都可以使用一个指定其到期期限的属性进行标记。以这种方式标记的每个函数都会被记忆,并将结果存储在缓存中,其中函数调用的散列和参数用作键。我在后端使用Velocity,用于处理缓存数据的分发。


1

是否有通用的解决方案或方法来创建缓存,以了解条目何时过时,从而确保您始终获得最新数据?

否,因为所有数据都不同。一分钟后,某些数据可能会“过时”,一小时后,一些数据可能会持续数天或数月。

关于您的特定示例,最简单的解决方案是为文件提供“缓存检查”功能,您可以通过getData和调用它们transformData


1

没有通用解决方案,但:

  • 您的缓存可以充当代理(拉)。假设您的缓存知道最后一次更改的时间戳记,当有人调用时getData(),缓存会询问其最后一次更改的时间戳记的来源,如果相同,它将返回缓存,否则将使用源代码更新其内容并返回其内容。(一种变体是客户端直接在请求上发送时间戳,如果时间戳不同,则源将仅返回内容。)

  • 您仍然可以使用通知过程(推送),缓存观察源,如果源发生更改,它将向缓存发送一条通知,然后将其标记为“脏”。如果有人调用getData()缓存,将首先更新到源,删除“脏”标志;然后返回其内容。

一般来说,选择取决于:

  • 频率:许多调用getData()都希望推送,以避免源被getTimestamp函数淹没
  • 您对源的访问权限:您是否拥有源模型?如果没有,您可能无法添加任何通知过程。

注意:由于使用时间戳是http代理工作的传统方式,因此另一种方法是共享存储内容的哈希。我知道让2个实体一起更新的唯一方法是我叫你(拉)或你叫我…(推)。



-2

也许忽略缓存的算法将是最通用的算法(或者至少是较少依赖硬件配置的算法),因为它们将首先使用最快的缓存并从那里继续前进。这是麻省理工学院的讲座:缓存忽略算法


3
我认为他不是在谈论硬件缓存,而是在谈论他的getData()代码具有将文件中获取的数据“缓存”到内存中的功能。
Alex319
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.