扩展挑战 第3部分:Cache统治一切

发布: (2026年2月6日 GMT+8 02:19)
10 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将为您翻译成简体中文。

它像所有虚假的黎明一样,以好消息开始。

Postgres Pete 很镇定。
团队在庆祝。有人做了个 meme。

但有点不对劲。

  • 不是“应用宕机”那种坏事。只是…有点软绵绵。
  • 已登录用户的延迟会出现峰值。
  • 报表生成仍然占用了大量 CPU。
  • 每当有人访问主页时,你的服务器本周已经第 400,000 次运行同样的查询。

你不再是慢了,而是浪费了资源。

欢迎来到第 3 章: 你的架构终于足够快,以至于可以显露出你到底重复做了多少工作。

战争故事:把一切都熔化的排行榜

几年前我们推出了一个游戏化活动:排行榜、徽章、每日奖励

关键点?
我们在 每一次请求 时都重新计算排行榜。

  • 每一次点击都会在数百万行数据上进行一次大规模的排序、连接和过滤。
  • 刚上线时不算什么——也许每小时 100 名用户。
  • 当它走红时?一天 90,000 次点击

我们的数据库没有崩溃,但性能糟透了。加入一个 60 秒的 Redis 缓存 后,响应时间从 912 ms → 38 ms,查询负载下降了 99.7 %

Postgres Pete 给我们写了一封感谢信。

第三章:缓存不是作弊

  • 在小规模时,缓存是可选的。
  • 在大规模时,缓存就是一切。

错觉是只要让数据库快、查询高效,你就没问题。
1,000 名用户访问同一路径 并且你生成 1,000 条相同的响应——恭喜,你把浪费优化到了极致。

缓存 是让你不再是快餐厨师……而成为拥有备餐站的厨师。

缓存层次

让我们按层而不是工具来拆解。
你不需要因为有人写了博客就使用 Redis。你需要为合适的工作选择合适的缓存类型。

1. 页面与片段缓存

何时使用位置
对每个用户都不变的完整页面响应WordPress、SSR 框架、营销页面、未登录视图
CDN 边缘缓存(Cloudflare、Fastly)用于静态资源静态 HTML 快照、组件级别的片段缓存(Next.js getStaticPropsgetServerSideProps 配合 revalidate

2. 查询结果缓存

何时使用位置
返回可预测结果的高消耗查询报表、排行榜、统计页面
将查询结果缓存到 Redis,30–300 秒在关键数据变更时失效或更新;使用确定性的缓存键,例如 leaderboard:daily:2025-07-06

3. 对象缓存

何时使用位置
访问频繁且不经常变更的实体用户设置、价格表、内容元数据
首次访问时将对象加载到缓存TTL + 写穿/读穿模式;使用命名空间键(user:42:profile)避免污染

4. 边缘缓存 & CDN

何时使用位置
静态资源、使用安全 GET 的 API、提升区域延迟Next.js、Shopify headless、任何全球提供静态内容的网站
示例GET /products?category=fitness → 缓存。POST /checkout → 不缓存。
失效使用代理键(surrogate keys),例如 product:updated → purge /products

Redis 示例:旁路缓存模式

// Basic cache-aside pattern
const getCachedUser = async (userId) => {
  const cached = await redis.get(`user:${userId}`);
  if (cached) return JSON.parse(cached);

  const user = await db.users.findById(userId);
  await redis.setex(`user:${userId}`, 300, JSON.stringify(user));
  return user;
};

具体示例:主页延迟

指标之前之后
首页加载时间680 ms112 ms
在 DB & API 上的占比90 %
Redis 命中率12 %89 %
DB 查询减少87 %

仅缓存三个组件(精选产品、博客预览、客户评价)就产生了最大的影响。

缓存失效:被遗忘的缓存一半

写入缓存很容易。
正确地使缓存失效可以将成熟系统与抱有希望的实验区分开来。

+------------------------+
|   Does the data        |
|   change often?       |
+------------------------+
          |
+----------------+----------------+
|                |                |
Yes              No
|                |
+--------------+  +--------------------+
| Can you hook |  | Use long TTL with  |
| into writes? |  | fallback refresh   |
+------+-------+  +--------------------+
       |Yes
       |
+--------------+
| Event-driven |
| invalidation |
+--------------+
       |No
       |
+--------------+
| Use low TTL  |
| w/ polling   |
+--------------+

缓存失效方法

基于时间(TTL)

  • 易于理解。
  • 容忍一定的陈旧性。
  • 对仪表盘、统计、定价等场景“足够好”。

事件驱动

  • 数据更新时使缓存失效。
  • 在 ORM 中使用钩子或使用发布/订阅系统。
  • 管理更困难,但更精准。
// In your product update handler:
await redis.del(`product:${product.id}`);
await redis.del(`category:${product.category}:featured`);

依赖追踪(高级)

  • 追踪哪些数据驱动哪些缓存条目。
  • 仅重建受影响的部分。
  • 需要纪律性和工具支持(否则会自讨苦吃)。

标志您的缓存是否正常工作

健康的缓存缓存出现问题
热路径的缓存命中率 > 80 %用户看到陈旧数据或不一致
在负载下首字节时间保持低位命中率低;失效策略过于激进
Redis/Memcached 使用可预测缓存条目是巨大的二进制块
缓存冲突(应用 vs CDN)

Cache Debugging: What to Watch

  • Hit‑rate metrics (Redis INFO statskeyspace_hits / keyspace_misses)。
  • Latency of cache reads vs DB reads。
  • TTL distribution – 键是否过早过期或根本不失效?
  • Invalidation logs – 确保每一次应该使键失效的写操作都真正执行了。
  • Memory pressure – 关注 used_memory 和驱逐策略。

TL;DR

  • 在正确层级缓存(页面、查询、对象、边缘)。
  • 为每种数据类型选择合适的 TTL 或事件驱动策略
  • 监控命中率和延迟;在数据陈旧或出现问题前进行调整。

通过有纪律的缓存策略,你可以将浪费的“够快”转变为真正高效、可扩展的性能。

# Cache Health Checklist

✅ 检查 INFO 统计信息

  • 查找 keyspace_hitskeyspace_misses

📈 记录慢速/未命中查询

  • 标记缓存静默失败的路由。

⚡️ 检测缓存击穿

  • 识别何时大量用户同时请求同一未缓存的项目。
  • 考虑使用锁定或 stale‑while‑revalidate 策略。

⏰ 跟踪 TTL 过期

  • 验证 TTL 是否与实际使用模式保持一致。

高级模式(当你准备好时)

Stale‑While‑Revalidate

  • 立即提供过期数据。
  • 在后台获取新数据并替换缓存中的内容。
  • 减少用户等待时间和感知延迟。
  • 实现方式:
    • HTTP 头部:Cache-Control: stale-while-revalidate=60
    • 在应用程序中使用自定义中间件。

Soft TTL + Refresh

  • 项目有 TTL,但在接近过期时在后台刷新。
  • 防止冷启动并保持热点项目活跃。
  • 适用于访问频繁但更新不频繁的数据。
  • 实现方式:
    • 异步作业队列
    • 中间件钩子

Sharded or Namespaced Caches

  • 使用键前缀来分隔缓存作用域。
    • 示例:tenant-42:user:profilelocale-en:settings
  • 防止键冲突并简化批量失效。
  • 采用结构化键命名约定,以支持未来的自动化。

缓存反模式

  • 在全局缓存用户特定或敏感数据 – 正在酝酿的 GDPR 违规。
  • 为每日变化的数据硬编码长 TTL – 快,但错误。
  • 缓存所有内容而没有清除策略 – 你最终会得到一个第二个、未受管理的数据库。

TL;DR – 有意缓存

  • 不要优化慢的东西 – 避免反复执行它们。
  • 为正确的问题选择合适的缓存。
  • 在上线前设计好你的缓存失效策略 在上线前
  • 监控 命中率,而不仅仅是缓存大小。
  • 缓存不是作弊;它是系统扩展的方式。
  • 你的应用不仅现在快——而且高效。
  • 但不要放松太久……你需要开始将读写工作负载分离。

敬请期待。
接下来: 在不对你的灵魂进行分片的情况下扩展读取

Back to Blog

相关文章

阅读更多 »

使用 Redis 优化后端查询

原始方法 舒适版 端点逻辑很简单: 1. 查询数据库 2. 按分数排序用户 3. 返回前 10 名 sql SELECT FROM users ORDER...