什么是缓存?


72

我不断听到有关y的性能问题x的信息,他们通过缓存解决了该问题。

或者,如何在程序代码中执行x,y,z会损害您的缓存能力。

即使在最新的播客之一中,杰夫·阿特伍德(Jeff Atwood)也谈到了它们如何缓存某些值以进行快速检索。

“缓存”和“缓存”这两个术语似乎有些含糊,这使我对在不同情况下的含义感到困惑。无论您是指应用程序缓存还是数据库缓存,CPU等,这意味着什么。

什么是缓存?有哪些不同的类型?

从上下文中,我可以了解一下,将经常检索到的值存储到主内存中并可以快速查找它。但是,什么是它 真的

这个词似乎在许多不同的上下文中使用,其含义略有不同(cpu,数据库,应用程序等),我真的很想清除它。

在应用程序中的缓存工作方式与数据库缓存之间是否有区别?

当有人说他们发现一段会损害缓存的代码,并且在修复它后,它提高了应用程序的速度,他们在说什么?

程序的缓存是否是自动完成的?您如何允许将值缓存在程序中?我经常阅读该网站上的用户说他们在应用程序中缓存了一个值,我坐在这里想知道他们的意思。

另外,当有人谈论数据库缓存时,这实际上意味着什么?这仅仅是他们在数据库中打开的功能吗?您是否必须显式缓存值,还是数据库选择要缓存的值?

我如何开始自己缓存项目以提高性能?

您能举一些例子说明如何开始在应用程序中缓存值吗?还是再次说明,这是已经完成的事情了,我只需要以特定的方式编写代码以允许“缓存”即可?

那么数据库缓存又如何呢?我听说过像memcache之类的东西。在数据库中缓存是否需要这种实用程序?

我希望在应用程序缓存与数据库缓存,如何使用缓存以及在两种情况下如何实现实现之间实现良好的区分。


3
如果您打算投票关闭,请说明原因。
KingNestor

3
这是一个完全可以的问题。谁要关闭它,谁就错了。+1
mmcdole

Answers:


55

缓存只是一种将数据显式或隐式存储在高性能存储区(通常是内存)中并从中检索数据的做法。

让我解释。内存访问比文件,远程URL(通常),数据库或您喜欢的任何其他外部信息存储要快。因此,如果使用这些外部资源之一的行为很重要,那么您可能会受益于缓存以提高性能。

克努斯曾经说过,过早的优化是万恶之源。好吧,就我而言,过早的缓存是所有头痛的根源。直到你不解决问题一个问题。您做出的每个决定都会付出一定的成本,您现在必须付出代价才能实施它,然后再为以后进行更改而付出代价,这样您就可以推迟做出决定和更改系统的时间越长越好。

因此,首先确定您确实有问题以及问题所在。性能分析,日志记录和其他形式的性能测试将在这里为您提供帮助。我无法强调这个步骤的重要性。我见过人们“优化”没有问题的东西的次数令人震惊。

好的,所以您遇到了性能问题。假设您的页面正在运行需要很长时间的查询。如果这是读物,那么您有多种选择:

  • 将查询作为一个单独的过程运行,并将结果放入缓存中。所有页面仅访问高速缓存。您可以适当地频繁更新缓存的版本(每天一次,每周一次,每5秒更新一次,视情况而定);
  • 通过您的持久性提供程序ORM或其他方式透明地进行缓存。当然,这取决于您使用的技术。例如,Hibernate和Ibatis支持查询结果缓存。
  • 如果结果不在缓存中(或者它是“过时的”,这意味着它的计算早于指定的“年龄”),则让页面运行查询并将其放入缓存中。如果两个(或多个)单独的进程都决定它们需要更新结果,那么这将带来并发性问题,因此您最终一次运行相同的(昂贵的)查询八次。您可以处理此锁定高速缓存的操作,但这会导致另一个性能问题。您还可以使用您的语言的并发方法(例如Java 5并发API)。

如果是更新(或需要在读缓存中反映的更新),则要复杂一点,因为缓存中的旧值和数据库中的新值不好,因此您无法提供页面数据视图不一致。但是从广义上讲,有四种方法可以解决此问题:

  • 更新缓存,然后将更新相关存储的请求排队;
  • 直写式缓存:缓存提供者可以提供一种机制来保持更新并阻止调用者,直到进行更改为止。和
  • 后写缓存:与写后缓存相同,但不会阻塞调用方。更新异步且分别进行;和
  • 持久性即服务模型:假设您的缓存机制支持某种可观察性(即缓存事件侦听器)。基本上,一个完全独立的过程(调用者不知道)监听缓存更新,并在必要时将其持久化。

您选择上述哪种方法在很大程度上取决于您的要求,所使用的技术以及其他因素(例如,是否需要群集和故障转移支持?)。

很难比这更具体的,给你做什么指导不知道太多关于您的问题更详细的(像你是否有问题)。


我担心的是,如果数据库的所有部分都是缓存的一部分,那么数据库有什么用呢,我知道我们应该只缓存大多数请求或最繁忙的资源,但是它们占用了大部分空间,并且如果我在缓存它们,然后为什么要将它们存储在DB中?只是因为我不能相信内存数据能永远保持下去。.我仍然对缓存的真正帮助感到困惑,redis服务器的服务速度比mysql快,这很好,那么如果我要存储几乎所有内容,为什么我应该拥有MYSQL redis
Aman Virk 2014年

1
您所说的“透明缓存”是什么意思,什么是“持久性提供程序”?
特工Zebra

“除非遇到问题,否则不要解决问题。” --ANALYSIS--
Devang

@amanVirk受约束的值通常存储在某种形式的随机存取存储器中,该速度更快,但价格更高。您无法缓存所有内容。通常,您只能保持高速缓存的容量,并且一旦高速缓存开始增长到超出限制,就必须丢弃旧条目(取决于高速缓存策略)。如果您的缓存足够大,它将始终使工作数据集保持关闭状态。如果您的缓存远远小于需求,则可能会影响性能,因为大多数缓存请求都是未命中而不是命中。
Fardin

@cletus,如何使用Hazelcast更新缓存?谢谢你
Diego Ramos

14

您很可能会阅读有关Web应用程序上下文中的缓存的信息。由于Web的性质,缓存可以带来很大的性能差异。

考虑以下:

网页请求到达Web服务器,然后将请求传递给应用程序服务器,该服务器执行一些呈现页面的代码,该代码需要转到数据库以动态检索数据。

此模型无法很好地扩展,因为随着页面请求数量的增加,服务器必须为每个请求一遍又一遍地执行相同的操作。

如果Web服务器,应用程序服务器和数据库位于不同的硬件上并通过网络相互通信,则这将成为更大的问题。

如果您有大量用户点击此页面,则对于每个请求都不要一直访问数据库。相反,您诉诸于不同级别的缓存。

结果集缓存

结果集缓存将数据库查询的结果与查询一起存储在应用程序中。每次网页生成查询时,应用程序都会检查结果是否已缓存,如果已缓存,则将其从内存数据集中提取。应用程序仍然必须呈现页面。

组件缓存

网页由不同的组件-小页面,或您可能要称呼的任何页面组成。组件缓存策略必须知道用于请求组件的参数。例如,网站上的一个“最新新闻”栏会使用用户的地理位置或偏好来显示本地新闻。因此,如果某个位置的新闻被缓存,则不需要渲染该组件,并且可以从缓存中提取该组件。

页面缓存

缓存整个页面的一种策略是将查询字符串和/或标头参数与完全渲染的HTML一起存储。文件系统的运行速度足够快-与调用应用程序服务器以呈现页面相比,Web服务器读取文件的成本仍然更低。在这种情况下,每个发送相同查询字符串的用户都将获得相同的缓存内容。

智能地组合这些缓存策略是为大量并发用户创建真正可扩展的Web应用程序的唯一方法。如您所见,潜在的风险是,如果不能通过密钥唯一地标识缓存中的一部分内容,人们将开始看到错误的内容。这会变得非常复杂,尤其是当用户进行会话并且存在安全上下文时。


5

我知道两个意思。


一种是应用程序缓存。在这种情况下,如果数据从某个地方(例如,通过网络)获取速度缓慢或计算速度缓慢,则应用程序将缓存数据副本(因此无需再次获取或重新计算):已经缓存)。实现缓存需要一些额外的应用程序软件(使用缓存的逻辑)和额外的内存(用于存储缓存的数据)。

这就是您在此处引用时使用的“缓存”:

从上下文中,我可以了解一下,将经常检索到的值存储到主内存中并可以快速查找它。


另一个是CPU缓存,其描述在此Wikipedia文章对此进行了介绍。CPU缓存自动发生。如果您从少量内存中进行大量读取,则CPU可以从其缓存中进行大部分读取。OTOH如果您从大量内存中读取数据,则无法完全容纳在高速缓存中,并且CPU必须花更多时间处理较慢的内存。

这就是您在此处引用时使用的“缓存”:

当有人说他们发现一段会损害缓存的代码,并且在修复它后,它提高了应用程序的速度,他们在说什么?

这意味着他们找到了一种重新排列代码以减少Cache丢失的方法


至于数据库缓存,我不知道。


4

有几个问题。

一是粒度。您的应用程序可以在数据库之上进行非常精细的缓存。例如,数据库可能只缓存数据页面,而不必缓存特定的行。

另一件事是,应用程序可以以“本机”格式存储数据,而数据库显然仅以其内部格式进行缓存。

简单的例子。

假设你有一个数据库,它是由列的用户:USERIDFIRSTNAMELASTNAME。非常简单。

您希望将User加载USERID=123到您的应用程序中。涉及哪些步骤?

  1. 发出数据库调用
  2. 解析请求(SELECT * FROM USER WHERE USERID = ?
  3. 计划请求(即系统将如何获取数据)
  4. 从磁盘中获取数据
  5. 将数据从数据库流传输到应用程序
  6. 将数据库数据转换为应用程序数据(即,USERID转换为整数,例如,将名称转换为字符串)。

数据库缓存可能会缓存步骤2和3(这是语句缓存,因此它不会解析或重新计划查询),并缓存实际的磁盘块。

所以,这是关键。用户USER ID 123JESSE JAMES。您可以看到这不是很多数据。但是数据库正在缓存磁盘块。您有索引块(带有123),然后是数据块(具有实际数据,以及适合该块的所有其他行)。因此,名义上说60-70字节的数据实际上对4K-16K的DB具有缓存和数据影响(取决于块大小)。

光明的一面?如果您需要附近的另一排(例如USER ID = 124),则索引的可能性很高,并且已经缓存了数据。

但是,即使有了这种缓存,您仍然必须支付在数据线上移动数据的费用(除非您使用本地数据库,否则它总是在数据线上移动,这就是环回),并且您在“解组”数据。即,将其从数据库位转换为语言位,再转换为应用位。

现在,一旦Application获得其USER ID 123,它将值填充在一个长期存在的哈希图中。

如果应用程序再次需要它,它将在本地地图,应用程序缓存中查找并节省查找,电汇和编组成本。

应用程序缓存的阴暗面是同步。如果有人进来并执行了UPDATE USER SET LASTNAME="SMITH" WHERE USERID=123,则您的应用程序不会“知道”,因此缓存很脏。

因此,在处理这种关系方面有很多细节,可以使应用程序与数据库保持同步。

拥有大量的数据库缓存非常适合对“热”数据集进行大型查询。您拥有的内存越多,您可以拥有的“热”数据就越多。如果可以将整个数据库缓存在RAM中,那么就可以消除将数据从磁盘移动到RAM缓冲区的I / O(至少对于读取而言)延迟。但是您仍然要承担运输和编组费用。

应用程序可以更具选择性,例如,缓存更多有限的数据子集(DB仅是缓存块),并且使数据与应用程序“更近”可以提高性能。

不利的一面是,并非所有内容都缓存在应用程序中。与应用程序相比,数据库总体上倾向于更有效地存储数据。您还缺少针对应用程序缓存数据的“查询”语言。大多数人只是通过一个简单的密钥进行缓存,然后从那里去。容易找到USER ID 123,而“所有用过JESSE的用户”则更难。

数据库缓存往往是“免费的”,您可以设置一个缓冲区号,而DBMS则处理其余的。低影响,减少总体I / O和磁盘延迟。

应用程序缓存是特定于应用程序的。

它对于隔离的“静态”数据非常有效。那很容易。在启动时将大量内容加载到查找表中,如果它们发生更改,则重新启动应用程序。这很容易做到。

之后,随着您添加“脏”逻辑等,复杂性开始增加。

归结为,只要拥有Data API,就可以增量缓存。

因此,只要您getUser(123)到处调用而不是访问数据库,那么以后您就可以返回并添加缓存而getUser不会影响您的代码。

因此,我总是在每个人的代码中建议使用某种数据访问层,以提供抽象和拦截层。


您所说的“数据库缓存往往是“免费的”,“”是什么意思,“代码内”数据访问层的外观大致如何?在PHP或表达为例。
特工斑马

1
我指的是应用程序开发人员的“免费”。开发人员可以“免费”获得所有缓存功能,因为他们无需更改代码即可利用它。我无法与PHP或Express交流,因为我不熟悉它们。
Will Hartung 2015年

知道了谢谢。因此,在大多数情况下,缓存基本上是内置在数据库中的,并且在某种程度上是自动的?
特工斑马

2

缓存采用的是冗长或CPU密集型算法的结果,然后保存答案,这样就不必再次运行该算法,只需重用结果即可。


实际设置缓存的过程是什么?设置过程花了多少时间?
特工Zebra

2

缓存概念在这里是一个重载术语。我不熟悉数据库缓存的细节。

在应用程序中,该术语有两种用法。

当有人说他们发现一段会损害缓存的代码,并且在修复它后,它提高了应用程序的速度,他们在说什么?

在这种情况下,他们引用了CPU缓存。

CPU缓存是CPU内存,比RAM快很多,但是没有随机访问权限。CPU决定加载到缓存中的内容可能会有些复杂。请参阅Ulrich Dreppers 。每个程序员都应了解有关内存的许多详细信息。

注意CPU缓存可以很好地加快处理速度-您只需要多注意一些事情,它们在物理内存中的相对位置以及何时可以使用。

一个示例(也可能是可维护性的反模式)是,您拥有一个结构数组,并且对结构的成员进行了很多循环,而对于所有字段都是数组的结构,可能会更好。如果要循环的数据在内存中是连续的,则您更有可能不破坏缓存。

各种各样的事情都会影响缓存的使用效率-加载到缓存中的代码的分支预测,数据结构和访问模式的大小和对齐方式,何时何地声明要放入堆栈的局部变量。

该术语在应用程序编程中的其他常见用法可以通过称为备忘录的方式来完成。维基百科页面上的析因示例比我本来可以更好地说明事情。


1

数据库中的缓存通常是数据库的功能,并且由数据库自动管理。应用程序的缓存在一个平台之间会有所不同。

对象缓存是一种可用于将常用对象放入内存的机制,因此您无需支付检索数据和重新创建它们的费用。通常,这是通过代码进行管理的,并且取决于所使用的缓存解决方案。

有分布式的缓存解决方案,涉及在多个服务器上设置服务,以便为您提供各种缓存场。这提供了可伸缩性和冗余性。客户端可以通过网络请求缓存的信息。同样,这是代码中的手动过程。memcached是分布式缓存提供程序的一个示例:

http://www.danga.com/memcached/

一种特定类型的缓存示例是asp.net缓存。Asp.net支持多种缓存。有传统的对象缓存(可以在各种.net应用程序中使用,而不仅仅是网站)。还具有缓存功能,可让您配置页面和用户控件以自动缓存其输出。这不缓存数据,而是缓存最终结果(页面的HTML),并在用户使用与前一个用户相同的查询字符串参数请求相同的页面时提供最终结果。


数据库中的缓存是自动的,您是说数据库只是知道这样做吗?还是您必须对数据库进行编程才能做到这一点?例如,使用Redis / Postgres或Mongo DB
Agent Zebra

0

这可能比您想像的要容易得多,这就是人们试图关闭它的原因。

这只是意味着将值存储在内存中,而不是每次都将其返回数据库。

有很多方法可以做到这一点,但是这个概念本身是微不足道的。

编辑:也可以在任何级别上完成-花费很长时间的任何内容都可以缓存在可以更快访问的位置。


5
那根本不是一个人关闭这个问题的借口。我们迎合所有技能水平。不仅如此,而且如果他们认为缓存很简单,那么他们显然对此一无所知。
mmcdole

0

缓存不一定只适用于“经常检索”的值,而是适用于可以减少重新计算次数来节省时间的任何内容。我想到的一个简单示例是计算斐波契数列。最简单的递归实现如下所示(以伪代码):

function f(n)
    if n < 2 then
        return n;
    return f(n - 1) + f(n - 2)

可以通过缓存来改进此功能,以防止重新计算已知值:

fib_cache = {}

function f(n)
    if n < 2 then
        return n;
    if fib_cache.contains(n) then
        return fib_cache[n]
    fib_cache[n] = f(n - 1) + f(n - 2)
    return fib_cache[n]

从技术上讲,这本身不是“缓存”,而是“记忆”
FryGuy

正要发布相同的内容。引用维基百科的话:“虽然与缓存有关,但是记忆是指这种优化的一种特殊情况,将其区别开来。”
mmcdole
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.