使用MongoDB时,还有必要引入缓存层吗?
目录
为什么 Memcached 和 Redis 这样的缓存会被发明出来,且为何它们如此盛行? 那么,引入缓存层有什么问题吗? MongoDB 有何不同? AI 是怎么看的? 总结 了解更多关于 MongoDB 设计评审的信息
我经常会为那些正从关系型数据库迁移到 MongoDB 的应用程序进行设计评审。在这些评审中,客户通常会展示一张架构图,显示在应用服务器和 MongoDB 之间存在一个缓存层(通常是 Redis)。
我倾向于让架构保持尽可能简单——毕竟,每一层架构都会带来额外的复杂性和管理成本。因此,我会询问为什么要设置这个缓存层。当然,得到的回答总是为了加快数据访问速度。这反映出人们对缓存层创建的初衷以及 MongoDB 所提供的功能存在误解。
我参加过的设计评审中,几乎没有一次是不建议移除缓存层的。
所以,回答本文标题中的问题——什么时候应该在 MongoDB 中使用缓存?——答案很可能是:永远不需要。本文旨在解释原因,但如果你读完后仍然认为你的应用需要缓存,我很乐意与你探讨你的应用程序。
为什么 Memcached 和 Redis 这样的缓存会被发明出来,且为何它们如此盛行?
引入缓存层是因为应用程序直接从关系型数据库读取所需数据太慢了。
这是否意味着在 Oracle、DB2、Postgres、MySQL 等数据库上工作的开发人员不够聪明?为什么这些开发人员不能让关系型数据库变得更快?事实是,所有这些数据库都是由优秀的开发人员编写的,他们内置了索引、内部数据库缓存以及其他功能,以尽可能快地读取记录。
问题在于,应用程序很少只需要从规范化的关系型数据库中读取单条记录。相反,它通常需要跨多个表执行多次连接(Join)才能构成一个完整的业务对象。这些连接代价高昂(速度慢且消耗大量资源)。因此,应用程序不希望每次读取同一个业务对象时都承担这种代价。这就是缓存层产生价值的地方——将规范化的关系型数据连接一次,然后缓存结果,以便应用程序能够高效地多次获取相同的结果。
此外还有数据分布的问题。大多数关系型数据库设计于 50 年前,当时企业通常在单个数据中心运行数据库和应用程序。时至今日,企业和客户遍布全球,每个人都希望处理相同的数据。你肯定不希望全球分布的应用服务器因为从位于另一个大洲的数据库不断获取相同数据而遭受延迟和高额开销。你希望在每个需要数据的应用服务器附近都能拥有数据的本地副本。
关系型数据库在设计时并未考虑这种数据分布需求。RDBMS 供应商试图通过各种变通方案来解决这个问题,但效果远非理想。因此,许多企业选择将数据分布任务委托给分布式缓存层。
请注意,Redis 和 Memcached 被广泛用于 Web 应用的会话管理(Session Handling),在这种情况下,持久性不是必需的。在这种场景下,缓存本身就是数据存储(即,它不是位于应用和 MongoDB 之间的缓存层)。虽然你可以(而且确实有人这么做)使用 MongoDB 进行会话管理,但这超出了本文的讨论范围。
那么,引入缓存层有什么问题吗?
当你的数据库无法提供应用程序所需的性能和延迟时,引入缓存层通常是一个很好的解决方案。
然而,这个额外的数据层伴随着成本。显而易见的成本是提供缓存服务所需的软件许可和硬件投入。
不那么明显的是给开发人员带来的额外负担。他们需要掌握一种新的查询语言(甚至可能是新的编程语言)。当 RDBMS 中的数据发生变化时会怎样?这些变化如何传播到你的缓存层?
因此,缓存层必须通过提供比直接访问数据库更显著的效益,才能证明其存在的价值。
MongoDB 有何不同?
在 MongoDB 中,我们希望你以一种能够高效满足应用程序最频繁查询(或那些 SLA 要求最严苛的查询)的方式来组织数据结构。MongoDB 通过允许单条记录(文档)包含嵌入(嵌套)对象来映射对象结构。对数组的支持使得无需连接多个集合即可实现“一对多”和“多对多”关系。
在许多情况下,应用程序所需的业务对象可以直接映射到一个 MongoDB 文档。在其他情况下,它可能需要多个文档,这些文档可以通过单次索引查找来获取。
MongoDB 有其自身的内部 LRU(最近最少使用)缓存,因此如果你的文档最近被访问过,它很可能已经驻留在内存中。因此,与 Redis 一样,MongoDB 可以通过从内存中获取单个文档/对象来满足应用程序的查询。
请注意,MongoDB 支持连接(Joins),但我们建议组织数据以尽量减少其使用。
缓存层的另一个增值点是分布式架构中的数据局部性。MongoDB 内置了此功能。MongoDB 副本集(Replica Set)有一个处理所有写入的主节点,以及最多 49 个从节点——每个从节点都拥有数据的副本。为了实现最低延迟的查询,你可以将从节点放置在每个应用服务器所在的本地位置。MongoDB 负责保持从节点数据与主节点同步,因此你无需编写和维护任何额外的同步代码。
AI 是怎么看的?
生成式 AI 生成的回复受到已发布信息的驱动,因此它应该代表了关于某个话题的普遍观点。我觉得看看 AI 对人们为何在 MongoDB 前放置缓存的“传统智慧”有何理解会很有意思。
我问了 ChatGPT 4o 这个问题:
“解释一下为什么我应该使用缓存层(例如 Redis),而不是让我的应用程序直接从 MongoDB 读取数据。”
我将总结并回应 ChatGPT 指出的缓存层的主要优势:
- “提升性能。Redis 完全在内存中运行,这使得它比依赖磁盘 I/O 获取数据的 MongoDB 快得多。” 如上所述,MongoDB 有自己的内存缓存,因此你频繁访问的文档将驻留在内存中,无需访问磁盘。
- “减轻 MongoDB 的负载。频繁直接访问 MongoDB 获取相同数据会增加查询负载,这可能会拖慢数据库,尤其是在高读取流量下。” MongoDB 是可扩展的。可以在副本集中添加额外的从节点来增加查询带宽。MongoDB 分片(Sharding)(分区)可以在水平方向上扩展数据容量或写入吞吐量。
- “处理高读取流量。读写比很高的应用(如 Web 应用、API)可以受益于 Redis 快速服务缓存数据的能力。” MongoDB 的数据库缓存提供了同样的优势,而且无需开发人员付出额外的同步数据变更的努力。
- “更快访问频繁使用的数据。Redis 是缓存频繁访问或热点数据(如用户会话、配置或产品详情)的理想选择。” 频繁访问的热点数据将保留在 MongoDB 的内存数据库缓存中。
- “降低地理分布式应用的延迟。通过将 Redis 缓存复制到更靠近终端用户的地方,可以避免从遥远位置查询 MongoDB 时的网络高延迟。” 通过在应用服务器站点附近放置副本,可以解决数据局部性问题。
- “支持数据过期(TTL)。Redis 具有内置的生存时间(TTL)功能,可以在指定时间后自动删除缓存数据。” MongoDB 使用 LRU 缓存,因此如果空间需要留给最近查询的数据,不再被查询的文档将从内存中移除。如果你想完全从数据库中删除文档,MongoDB 也有 TTL 索引;如果你想将它们移动到更廉价的存储中,可以使用 Atlas Online Archive。
- “成本效益。重复从 MongoDB 读取数据可能会消耗大量资源,尤其是涉及复杂查询时,这会导致基础设施成本增加。” 你的 MongoDB 模式应该经过设计,以确保重要的查询不需要复杂的处理。
- “支持专门的数据结构。Redis 支持列表、集合、有序集合、哈希和流等高级数据结构,而 MongoDB 不原生提供这些。” MongoDB 支持列表和集合。哈希可以通过包含键值对的文档数组在 MongoDB 中表示(即 MongoDB 属性模式)。MongoDB 时序集合满足了与 Redis 流相同的需求。
- “弹性和容错性。如果 MongoDB 暂时不可用或处于重负载下,缓存层可以作为后备。” MongoDB 可以纵向或横向扩展以满足任何负载需求。使用 MongoDB Atlas 时,扩展可以自动化。MongoDB 副本集为读取和写入提供了容错能力。
- “简化复杂查询结果。MongoDB 计算频繁请求的复杂查询(如聚合、连接)可能需要时间。” 你的 MongoDB 模式应该设计为避免频繁运行复杂查询。结果可以存储(缓存)在 MongoDB 物化视图中,从而避免重复执行相同的复杂查询/聚合。
请注意,你从 ChatGPT 得到的回复在很大程度上取决于你的提问方式。如果我将提示词改为“解释一下为什么我不应该使用缓存层(例如 Redis),而应该让应用程序直接从 MongoDB 读取数据”,ChatGPT 很乐意劝阻我添加缓存层,并列举诸如增加系统复杂性、数据一致性问题、写入密集型工作负载的性能表现、成本、查询灵活性、维护和可靠性、小数据集(活动数据集可以完全放入 MongoDB 缓存中)以及实时报告等问题。
总结
当你的 RDBMS 无法提供应用程序所需的查询性能时,缓存层可以带来很大价值。在使用 MongoDB 时,记录数据库和缓存功能被合并在单一层中,为你节省了资金和开发时间。
分布式缓存可以弥补 RDBMS 的不足,但 MongoDB 内置了分布式功能。
如果你仍然认为你的应用程序从应用层和 MongoDB 之间添加缓存层中受益,请回复本文。我很乐意为你查看。
了解更多关于 MongoDB 设计评审的信息
设计评审 是来自 MongoDB 的设计专家为你提供如何最好地使用 MongoDB 构建应用程序建议的机会。评审旨在帮助你成功使用 MongoDB。申请评审永远不会太早。通过尽早与我们接触(甚至在你决定使用 MongoDB 之前),我们可以在你拥有最佳行动机会时为你提供建议。
本文解释了如何设计符合应用程序数据处理方式的 MongoDB 模式,从而在无需缓存层的情况下满足你的性能需求。