如果数据库已经提供缓存,为啥还要使用应用程序级缓存?

Posted

技术标签:

【中文标题】如果数据库已经提供缓存,为啥还要使用应用程序级缓存?【英文标题】:Why use your application-level cache if database already provides caching?如果数据库已经提供缓存,为什么还要使用应用程序级缓存? 【发布时间】:2011-02-27 04:11:25 【问题描述】:

现代数据库提供缓存支持。大多数 ORM 框架也会缓存检索到的数据。为什么需要这种复制?

【问题讨论】:

一些相关链接:docs.jboss.org/hibernate/stable/core/reference/en/html/… 以及:javalobby.org/java/forums/t48846.html 【参考方案1】:

因为要从数据库的缓存中获取数据,你仍然需要:

    从 ORM 的“本机”查询格式生成 SQL 对数据库服务器进行网络往返 解析 SQL 从缓存中获取数据 将数据序列化为数据库的在线格式 将数据反序列化为数据库客户端库的格式 将数据库客户端库的格式转换为语言级对象(即任何东西的集合)

通过在应用程序级别进行缓存,您无需执行任何操作。通常,这是对内存中哈希表的简单查找。有时(如果使用 memcache 进行缓存)仍然存在网络往返,但所有其他事情都不再发生。

【讨论】:

【参考方案2】:

以下是您可能想要这个的几个原因:

应用程序只缓存它需要的内容,因此您应该获得更好的缓存命中率 由于网络延迟,访问本地缓存可能比访问数据库快几个数量级 - 即使网络速度很快

【讨论】:

【参考方案3】:

使用强一致性缓存扩展读写事务

通过添加更多Replica nodes,可以相当容易地扩展read-only transactions。

但是,这不适用于主节点,因为它只能垂直缩放:

这就是缓存发挥作用的地方。对于需要在Primary节点上执行的读写数据库事务,缓存可以通过将其定向到强一致性缓存(如Hibernate二级缓存)来帮助您减少查询负载:

使用分布式缓存

在应用程序的内存中存储应用程序级缓存是有问题的,原因有很多。

首先,应用内存是有限的,所以可以缓存的数据量也是有限的。

其次,当流量增加并且我们想要启动新的应用程序节点来处理额外的流量时,新节点会从冷缓存开始,这会使问题变得更糟,因为它们会导致数据库负载达到峰值,直到缓存被填充有数据:

要解决此问题,最好将缓存作为分布式系统运行,例如 Redis。这样,缓存的数据量就不受单个节点内存大小的限制,因为可以使用分片将数据拆分到多个节点。

并且,当自动缩放器添加新的应用程序节点时,新节点将从同一个分布式缓存中加载数据。因此,不再存在冷缓存问题。

【讨论】:

关于应用程序级缓存的观点是有效的问题,但不是无法克服的不可克服的障碍。在“go”语言中有 google 的“group-cache”(github.com/golang/groupcache)可以非常优雅地处理其中一些问题。只是我的 2c 来帮助人们创建关于缓存选项方面当前可用/可实现的思维导图。【参考方案4】:

即使数据库引擎缓存了数据、索引或查询结果集,您的应用程序仍需要往返数据库才能从该缓存中受益。

ORM 框架与您的应用程序在同一空间中运行。所以没有往返。只是内存访问,一般会快很多。

框架还可以决定在需要时将数据保留在缓存中。当其他并发客户端发出使用缓存的请求时,数据库可能会决定在不可预知的时间使缓存数据过期。

您的应用程序端 ORM 框架还可能以数据库无法返回的形式缓存数据。例如。以 Java 对象集合而不是原始数据流的形式。如果您依赖数据库缓存,您的 ORM 必须重复该转换为对象,这会增加开销并降低缓存的好处。

【讨论】:

【参考方案5】:

此外,数据库的缓存可能并不像人们想象的那么实用。我从http://highscalability.com/bunch-great-strategies-using-memcached-and-mysql-better-together 复制了这个——它是特定于 MySQL 的。

鉴于 MySQL 有缓存,为什么还需要 memcached?

MySQL 缓存仅与一个实例相关联。这将缓存限制为一台服务器的最大地址。如果您的系统大于一台服务器的内存,那么使用 MySQL 缓存将不起作用。如果从另一个实例中读取相同的对象,则它不会被缓存。

查询缓存在写入时失效。你建立了所有的缓存,当有人写它时它就会消失。根据使用模式,您的缓存可能根本不是缓存。

查询缓存是基于行的。 Memcached 可以缓存您想要的任何类型的数据,并且不仅限于缓存数据库行。 Memcached 可以缓存复杂的复杂对象,无需连接即可直接使用。

【讨论】:

【参考方案6】:

已正确指出与网络往返有关的性能注意事项。

对此,必须补充的是,将数据缓存在 dbms(不是“数据库”)之外的任何地方都会产生一个问题,即可能已过时的数据仍然显示为“最新”。

屈服于性能改进的诱惑是以失去绝对可靠和有保证的正确和一致数据的保证(无懈可击或至少接近)为代价的。

每次准确性和一致性至关重要时都要考虑到这一点。

【讨论】:

【参考方案7】:

这里有很多很好的答案。我要补充一点:我知道我的访问模式,数据库不知道。

根据我在做什么,我知道如果数据最终过时,那并不是真正的问题。数据库没有,并且必须用新数据重新加载缓存。

我知道我会在接下来的一段时间内多次返回某个数据,因此保留它很重要。数据库必须猜测要在缓存中保留什么,它没有我所做的信息。因此,如果我一遍又一遍地从数据库中获取它,如果服务器很忙,它可能不在缓存中。我可能会出现缓存未命中。有了我的缓存,我可以确定我会成功。对于获取非平凡的数据(即一些连接、一些组函数)而不是仅获取一行的数据尤其如此。获取主键为 7 的行对数据库来说很容易,但如果它必须做一些实际工作,缓存未命中的成本要高得多。

【讨论】:

【参考方案8】:

毫无疑问,现代数据库正在提供缓存设施,但是当您的站点上有更多流量并且需要执行许多数据库事务时,您将无法获得高性能。因此,在这种情况下,为了提高性能,休眠缓存将帮你, 通过优化数据库应用程序。缓存实际上存储了已经从数据库加载的数据,这样当应用程序想要再次访问该数据时,我们的应用程序和数据库之间的流量就会减少。应用程序和数据库之间的访问时间和流量也会减少。

【讨论】:

【参考方案9】:

也就是说 - 缓存有时会成为一种负担,实际上会降低服务器的速度。当你有高负载时,缓存的算法和不适合的算法可能不适合传入的请求......你得到的是一个缓存,它开始像 FIFO 一样超时运行......这开始让自己知道当位于缓存后面的表的记录比内存中缓存的要多得多时...

一个很好的折衷办法是将数据集群化为您想要缓存的内容。拥有一个将更新推送到集群的主服务器,发送/推送更新的时间应该能够根据 TTL(生存时间)设置为每个表定制。

然后,您在用户节点上的逻辑和数据可以位于在内存数据库中打开的同一台服务器上,或者如果它确实必须获取数据,那么您可以将其设置为使用管道而不是网络调用...

这需要考虑一下您希望如何使用数据以及何时/如果您进行集群,那么您必须了解分布式事务(多个数据库上的事务)......但如果数据被缓存将自行更新,无需链接到其他数据库空间,然后您就可以摆脱这个....

ORM 缓存的问题在于,如果数据库是通过另一个应用程序独立更新的,那么 ORM 缓存可能会过时...此外,如果您对集合进行更新也会变得棘手...更新可能更新缓存中的内容,它需要某种算法来识别需要在内存中删除/更新哪些记录(减慢更新速度!?) - 然后这个算法变得非常棘手并且容易出错!

如果使用 ORM 缓存,那么请遵循一个简单的规则...缓存几乎不会更改的简单对象(例如用户/角色详细信息)并且尺寸很小并且在请求中被多次命中...如果它除此之外,我建议对数据进行聚类以提高性能。

【讨论】:

以上是关于如果数据库已经提供缓存,为啥还要使用应用程序级缓存?的主要内容,如果未能解决你的问题,请参考以下文章

有了内存 为啥还要有 cache(一级、二级、三级)以及寄存器

我配置了redis注解缓存,为啥不起作用

Hibernate二级缓存问题

Hibernate的缓存是在何时清除的?

HIbernate缓存

Hibernate 缓存介绍