site logo

Marico's space

Redis 缓存最佳实践与陷阱

算法解析 2026-07-24 17:34:15 6

Redis 缓存这块,之前几篇把常见的模式和问题都拆开讲过了。今天这篇把这些内容串起来,聊聊实际项目里该怎么用,以及那些几乎每个人都会踩的坑。说复杂也不复杂,但每一条背后都是有人在线上环境熬夜排查过的经验,照着做能省不少事。

只缓存值得缓存的数据

缓存会增加系统复杂度,得让它物有所值。值得缓存的数据特点是:读取频率远高于写入频率,而且生成成本高。比如多表关联查询、聚合计算的结果、渲染好的页面片段、不常变化的配置项。如果某个数据每次请求都在变,或者本来获取成本就很低,缓存它就是给自己找麻烦——平白多了一堆需要管理的 key,实际收益几乎为零。上缓存之前先问自己:这个数据真的是读多写少吗?真的慢吗?如果答案是否,就别上了。

反过来,不要看到什么都想往缓存里塞。每个缓存的 key 都是一块需要正确失效的内存占用。宁可少缓存,只缓存真正热的那些数据,也不要搞一个大而全的缓存池,最后发现一半以上的数据根本没人访问,还把自己搞得很累。

每个 key 都必须设 TTL

给每个缓存 key 加上 TTL(生存时间),哪怕你已经在代码里做了主动失效。这个 TTL 是最后的兜底:它限制数据陈旧程度、自动清理被遗弃的 key、在你某个地方忘记失效的时候救你一命。一个没有过期时间的缓存 key 就是一颗定时炸弹,要么导致内存泄漏,要么某天给你返回一份永远过期的数据。唯一可以不设 TTL 的情况是那些真正需要持久化的数据——但说实话,这种数据压根不应该放在缓存层。

key 的设计要上心

key 的命名不是小事。一个清晰、层次分明的命名规则(比如 entity:id:field 这种格式,user:1:profile 就是例子)能让整个 key 空间易于理解,方便批量失效,还能避免 key 冲突。这个规则尽早定下来,之后所有地方都照着来。另外,如果缓存的是结构化数据,在 key 里加一个版本号或 schema 标记。这样当数据结构变了(比如给缓存对象加了一个字段),新老代码不会因为格式不一致出问题——改个版本号就相当于让旧格式的数据自动失效。

缓存挂了也要能跑

你的应用必须能在 Redis 不可用时继续提供服务。cache-aside(旁路缓存)模式下这基本是免费的——缓存未命中时自动回源到数据库。但前提是你的代码要把 Redis 错误当作 miss 来处理,而不是直接崩掉。给缓存读取加个 wrapper,Redis 出错时只记个日志然后走数据库,千万别把错误往上抛变成 500。另外注意另一面:如果 Redis 挂了,所有请求都穿透到数据库,数据库就要承受之前被缓存挡掉的那部分流量。所以缓存挂了有时候会连带着把数据库打挂。限流和 stampede(缓存雪崩)防护在这里就很重要。

几个常见的坑

下面列几个高频踩雷的点:

  • 加了缓存但没想好怎么失效。 加缓存容易,保持缓存正确才是真正的活儿。决定缓存策略的时候就得想好失效方案,别等第一次出现数据不一致的 bug 再倒回来改。
  • 不设 TTL,或者所有 key 用同一个 TTL。 没有 TTL 会内存泄漏和持续返回脏数据;同一批缓存用相同的 TTL 会在同一时刻集体过期,引发 synchronized-expiry stampede(同步过期雪崩)。设 TTL,并且加上 jitter(抖动)打散过期时间。
  • 把用户相关数据存在共享 key 下。 把个性化返回结果存在没有用户维度的 key 里,会导致用户 A 看到用户 B 的数据。任何涉及个性化的数据,key 里都要带上用户 ID 或租户 ID。这既是正确性问题,也是安全漏洞。
  • 存过大的 value。 几 MB 的缓存 value 传输慢,还占内存。缓存真正需要的字段,不要把整个对象图都塞进去,时刻关注 value 大小。
  • 不管内存上限。 不配置 maxmemory 和淘汰策略,Redis 会一直吃内存直到被系统 kill。这两个必须设,之前 TTL 那篇有详细说明。
  • 把缓存当唯一数据源。 数据库才是数据的主人。如果一个值只存在于缓存里,缓存一丢数据就没了。缓存里放的应该是持久化数据的副本,不是唯一一份。

监控要跟上

你没法优化你看不见的东西。核心指标是 hit rate(命中率):命中次数除以总查询次数。命中率低说明缓存没帮上忙——可能是缓存了错误的数据、TTL 设太短、或者 key 碎片化太严重,这种情况下缓存纯粹是负担。还要监控内存使用量相对于 maxmemory 的比例、eviction 次数(淘汰次数高说明缓存太小或者装的太多)、以及延迟。Redis 通过 INFO 命令暴露这些指标,盯着它们就能知道缓存到底在不在干活。监控这块后续运维模块会深入讲。

总结一下

如果只记几条:从这个模块里挑最重要的:只缓存读多写少、生成成本高的数据;每个 key 都加 TTL 并打散过期时间;用户相关数据 key 里必须带用户或租户 ID;Redis 挂了走降级而不是崩溃;持续监控命中率。做到这些,能避免大多数缓存相关的事故,而每一条背后都有人付出过代价。

缓存是 Redis 价值最高的功能之一,也是最容易悄悄挖坑的地方。模式给你提供了工具,这些实践帮你避开常见的陷阱。缓存要有目的,过期要有兜底,作用域要谨慎,降级要优雅,最后拿数据说话。

缓存模块到这里就结束了。接下来我们进入 Redis 作为基础设施的使用场景,先聊聊 pub/sub(发布/订阅)消息系统怎么在系统各部分之间做实时通信。

核心要点

  • 只缓存读多写少、生成成本高的数据;什么都往缓存塞只会增加复杂度和失效管理的风险,收益却很低。
  • 每个缓存 key 都要设 TTL 作为兜底,哪怕代码里已经做了主动失效;加上 jitter 避免同一批 key 同步过期。
  • key 命名要统一且有层次,用户相关数据必须把用户 ID 或租户 ID 包含在 key 里,防止数据泄露。
  • Redis 挂了要走降级回源,而不是直接报错;同时要注意数据库会承受之前被缓存挡掉的全部流量。
  • 监控命中率、内存使用和淘汰次数;命中率低说明缓存是负担而不是帮手。