跳至主要内容

缓存策略:Web 应用程序的 Redis、CDN 和 HTTP 缓存

使用 Redis、CDN 边缘缓存和 HTTP 缓存标头实施多层缓存,可将延迟减少 90% 并降低基础设施成本。

E
ECOSIRE Research and Development Team
|2026年3月15日3 分钟阅读487 字数|

缓存是提高应用程序性能的最有效的技术。 设计良好的缓存可以将数据库负载减少 90%,将响应时间从 200 毫秒缩短到 2 毫秒,并每月节省数千美元的基础设施成本。然而,缓存也是软件工程中最容易被误解的领域之一——失效错误会产生过时的数据,缓存踩踏会导致服务器瘫痪,而选择不当的 TTL 要么会浪费内存,要么会提供过时的内容。

属于我们的性能与可扩展性系列

阅读完整指南

缓存是提高应用程序性能的最有效的技术。 设计良好的缓存可以将数据库负载减少 90%,将响应时间从 200 毫秒缩短到 2 毫秒,并每月节省数千美元的基础设施成本。然而,缓存也是软件工程中最容易被误解的领域之一——失效错误会产生过时的数据,缓存踩踏会导致服务器瘫痪,而选择不当的 TTL 要么会浪费内存,要么会提供过时的内容。

要点

  • 分层设计缓存:浏览器到CDN到应用程序(Redis)到数据库查询缓存——每一层处理不同的数据特征
  • Redis 缓存旁模式是最安全的默认模式:从缓存读取、回退到数据库、在未命中时填充缓存
  • HTTP 缓存标头(Cache-Control、ETag、Vary)完全消除不必要的请求 - 最快的请求是永远不会到达您的服务器的请求
  • 缓存失效比缓存更难——使用基于 TTL 的过期作为主要策略,并使用事件驱动的失效来保证关键数据的新鲜度

缓存层次结构

有效的缓存分层工作,每一层都针对不同的延迟、容量和新鲜度要求进行了优化。

层延迟产能最适合无效
浏览器缓存0 毫秒(本地)受设备限制静态资产、用户特定数据缓存控制标头
CDN 边缘缓存5-50 毫秒跨边缘节点的 TB 级静态资产、公共 API 响应清除 API、TTL
应用程序缓存(Redis)1-5毫秒千兆字节(RAM 限制)会话数据、计算结果、速率限制TTL+事件驱动
数据库查询缓存10-50 毫秒可配置重复相同的查询自动表写入

第 1 层:浏览器缓存

浏览器缓存是最快且最便宜的缓存,因为它完全消除了网络请求。 HTTP Cache-Control 标头控制浏览器缓存行为。

对于具有内容哈希文件名的静态资源(例如 Next.js 构建输出),请设置 Cache-Control: public, max-age=31536000, immutable。文件名中的内容哈希可确保更改的内容获得新的 URL,因此您可以积极缓存,而不必担心内容过时。

对于 HTML 页面和 API 响应,请使用较短的 TTL 进行重新验证:Cache-Control: public, max-age=60, stale-while-revalidate=300。这会提供缓存内容 60 秒,然后在后台重新验证,同时继续提供过时内容最多 5 分钟。

第 2 层:CDN 边缘缓存

CDN 在分布于全球的边缘服务器上缓存内容,通过从最靠近每个用户的位置提供内容来减少延迟。对于全球用户群,CDN 缓存将平均延迟从 200-500 毫秒(原始服务器往返)减少到 10-50 毫秒(最近边缘)。

在 CDN 上缓存什么:

  • 所有静态资源(JavaScript、CSS、图像、字体)
  • 公共营销页面(具有较短的 TTL 以确保内容新鲜度)
  • 产品目录页面(电子商务 5-15 分钟 TTL)
  • 公共数据的 API 响应(产品列表、博客内容)

不要在 CDN 上缓存什么:

  • 经过身份验证的 API 响应(用户特定数据)
  • 购物车和结帐页面
  • 管理面板页面
  • Webhook 端点
  • 任何带有 Set-Cookie 标头的响应

第 3 层:应用程序缓存 (Redis)

Redis 提供对缓存数据的微秒级延迟访问,使其成为计算成本昂贵或频繁访问的数据的理想选择。与 CDN 缓存不同,Redis 可以缓存经过身份验证的数据和用户特定的数据,因为您的应用程序控制访问。

第 4 层:数据库查询缓存

PostgreSQL 维护一个缓冲区高速缓存(shared_buffers),将频繁访问的表和索引页缓存在内存中。虽然每个查询不能直接控制,但正确的配置可确保热数据保留在内存中。对于报告查询,请考虑预先计算昂贵聚合的物化视图。


Redis 缓存模式

Redis 是最流行的 Web 应用程序级缓存,支持字符串、哈希、列表、集合、排序集和流。您选择的模式决定了缓存在边缘情况下的行为方式。

缓存旁路(延迟加载)

Cache-aside 是最安全的默认模式。应用程序首先检查Redis。当缓存命中时,它返回缓存的数据。当缓存未命中时,它会查询数据库,将结果与 TTL 一起存储在 Redis 中,然后返回数据。

优点:

  • 仅缓存请求的数据(未使用的数据不会浪费内存)
  • 数据库故障仅影响缓存未命中,不影响缓存命中
  • 易于实施和推理

缺点:

  • 每个密钥的第一个请求始终会命中数据库(冷启动)
  • 数据库更新和缓存过期之间可能存在陈旧数据

直写式

在直写式缓存中,每次数据库写入也会立即更新缓存。应用程序在同一操作中写入数据库和 Redis。

优点:

  • 缓存始终是新鲜的——没有陈旧的数据窗口
  • 读取性能始终很快(初始写入后没有缓存未命中)

缺点:

  • 写入延迟增加(每个操作两次写入)
  • 缓存可能包含从未读取的数据(浪费内存)
  • 当一个写入成功而另一个写入失败时需要仔细的错误处理

后写(回写)

后写式缓存会立即写入 Redis,但会将数据库写入延迟到后台进程。这可以减少写入延迟,但如果 R​​edis 在数据库写入完成之前发生故障,则会带来数据丢失的风险。

谨慎使用 - 此模式适用于分析计数器、速率限制和可以接受偶尔数据丢失的会话数​​据,而不适用于金融交易或库存盘点。

缓存踩踏预防

当流行的缓存键过期并且数百个并发请求同时查询数据库以重建数据库时,就会发生缓存踩踏。这可能会使数据库过载。

预防策略:

  1. Stale-while-revalidate——在一个请求在后台重建缓存时提供稍微陈旧的数据
  2. 互斥锁定 - 使用 Redis SETNX 确保只有一个请求重建缓存,而其他请求则等待或提供过时的数据
  3. 概率提前过期 -- 在 TTL 过期之前随机重新计算,随着时间的推移分散重建而不是在过期时集中重建

TTL策略设计

生存时间 (TTL) 值决定数据在缓存中保留的时间。太短就会导致过多的缓存未命中。时间太长,您会提供陈旧的数据。

数据类型推荐TTL理由
用户会话30 分钟(滑行)平衡安全性与用户体验
产品目录5-15 分钟产品变化不频繁,新鲜度对定价很重要
搜索结果1-5 分钟结果随着库存更新而变化
静态内容(关于、常见问题解答)1-24 小时内容很少变化
速率限制计数器匹配窗口大小速率限制必须精确才能发挥作用
计算仪表板5-30 分钟平衡新鲜度与计算成本
功能标志30-60 秒旗帜变化的快速传播

滑动 TTL 与固定 TTL

固定 TTL 会使密钥在创建后的设定时间过期。每次访问密钥时,滑动 TTL 都会重置过期时间。对会话数据使用滑动 TTL(保持活动会话处于活动状态),对内容缓存使用固定 TTL(确保从源定期刷新)。


HTTP 缓存标头深入探讨

HTTP 缓存非常强大,因为它在每一层都起作用——浏览器、CDN、代理和负载均衡器都理解相同的标头。

缓存控制指令

  • public -- 任何缓存(浏览器、CDN、代理)都可以存储响应
  • 私有 -- 只有浏览器可以缓存(不是 CDN 或代理) -- 用于经过身份验证的响应
  • no-cache -- 缓存响应,但在使用之前与服务器重新验证(误导性名称 -- 它确实缓存)
  • no-store -- 根本不缓存(银行页面等敏感数据)
  • max-age=N -- 缓存有效期为N秒
  • s-maxage=N -- CDN/代理缓存持续时间(覆盖共享缓存的 max-age)
  • stale-while-revalidate=N -- 提供过时内容 N 秒,同时在后台重新验证
  • 不可变 -- 内容永远不会改变(与内容哈希 URL 一起使用)

ETag 和条件请求

ETag 提供基于内容的缓存验证。服务器生成响应内容的哈希值并将其作为 ETag 标头发送。在后续请求中,浏览器会在 If-None-Match 标头中发送 ETag。如果内容没有改变,服务器会响应304 Not Modified(无正文),从而节省带宽。

使用 ETag 进行 API 响应,其中内容变化不可预测,并且基于 TTL 的缓存要么过于激进,要么过于保守。

改变标题

Vary 标头告诉缓存响应取决于特定的请求标头。例如,Vary: Accept-Encoding 表示 gzip 和 Brotli 版本分别缓存。 Vary: Accept-Language 分别缓存不同的语言版本。

请小心使用 Vary——不同标头的每个独特组合都会创建一个单独的缓存条目。 Vary: Cookie 有效地禁用缓存,因为每个用户都有不同的 cookie。


缓存失效策略

众所周知,缓存失效是计算机科学中的两个难题之一。目标是在源数据更改时删除或更新缓存数据,而不会引入过时读取或不必要的缓存未命中。

基于 TTL 的过期时间

最简单、最可靠的失效策略。为每个缓存的键设置 TTL,并接受 TTL 窗口内数据可能稍微过时的情况。对于大多数用例,5 分钟的 TTL 可在新鲜度和性能之间提供出色的平衡。

事件驱动的失效

当数据库记录发生变化时,发布触发缓存删除的事件。这提供了近乎即时的新鲜感,但增加了复杂性和故障模式。如果事件丢失(网络问题、队列故障),缓存将提供陈旧数据,直到 TTL 过期。

对库存计数和定价等关键数据使用事件驱动的失效,并依赖 TTL 处理其他所有数据。

基于标签的失效

一些缓存系统支持使用标签来标记缓存条目。当您使标签失效时,带有该标签的所有条目都将被清除。这对于使与特定实体相关的所有缓存数据无效(例如,更新产品目录时所有与产品相关的缓存)非常有用。


常见问题

我如何决定缓存什么?

缓存经常读取、计算成本较高且能够容忍短暂陈旧的数据。产品目录页面、用户权限、计算仪表板指标和配置数据都是很好的候选者。购物车、实时库存盘点和财务交易通常不应缓存,或者应使用非常短的 TTL 和事件驱动的失效。

Redis 宕机时会发生什么?

通过缓存旁路模式,您的应用程序可以直接查询数据库。响应时间增加,但应用程序仍然正常运行。设计您的应用程序以优雅地处理缓存未命中——Redis 应该是一种性能优化,而不是单点故障。

我应该为 Redis 分配多少内存?

监控缓存命中率和内存使用情况。命中率高于 95%,内存利用率低于 80%,表明大小调整良好。如果命中率低于 90%,则说明您需要更多内存或 TTL 太短。大多数应用程序从 1-2GB 开始,并根据监控数据进行扩展。

我应该使用 Redis 还是 Memcached?

对于大多数应用程序来说,Redis 是更好的默认选择。它支持更多数据类型、持久性选项、用于事件驱动失效的发布/订阅以及用于原子操作的 Lua 脚本。对于极端规模的纯键值缓存,Memcached 更简单且速度稍快,但 Redis 涵盖了更多用例。

如何防止在部署后提供过时的数据?

在缓存键中包含版本标识符(例如,带有部署版本或架构版本的前缀键)。部署时,新版本使用新的缓存密钥,旧密钥通过 TTL 自然过期。或者,如果您的应用程序可以正常处理冷缓存,则可以在部署时刷新整个缓存。


下一步是什么

首先为您的静态资产和公共页面实现 HTTP 缓存标头——这可以在零应用程序更改的情况下立即提高性能。然后为最昂贵的数据库查询和 API 端点添加 Redis 缓存。

有关完整的性能优化图片,请参阅我们关于扩展您的业务平台 的支柱指南。要优化为缓存提供数据的数据源,请阅读我们的数据库查询优化 指南。

ECOSIRE 为 Odoo ERP 和 Shopify 上运行的平台实现缓存架构。 联系我们的团队 进行缓存策略审查。


由 ECOSIRE 发布 — 通过 Odoo ERP、Shopify 电子商务 和 OpenClaw AI 等人工智能驱动的解决方案帮助企业扩展规模。

E

作者

ECOSIRE Team

技术写作

ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。

ECOSIRE

与 ECOSIRE 一起发展您的业务

跨 ERP、电子商务、人工智能、分析和自动化的企业解决方案。