
Redis 缓存这块,之前几篇把常见的模式和问题都拆开讲过了。今天这篇把这些内容串起来,聊聊实际项目里该怎么用,以及那些几乎每个人都会踩的坑。说复杂也不复杂,但每一条背后都是有人在线上环境熬夜排查过的经验,照着做能省不少事。
缓存会增加系统复杂度,得让它物有所值。值得缓存的数据特点是:读取频率远高于写入频率,而且生成成本高。比如多表关联查询、聚合计算的结果、渲染好的页面片段、不常变化的配置项。如果某个数据每次请求都在变,或者本来获取成本就很低,缓存它就是给自己找麻烦——平白多了一堆需要管理的 key,实际收益几乎为零。上缓存之前先问自己:这个数据真的是读多写少吗?真的慢吗?如果答案是否,就别上了。
反过来,不要看到什么都想往缓存里塞。每个缓存的 key 都是一块需要正确失效的内存占用。宁可少缓存,只缓存真正热的那些数据,也不要搞一个大而全的缓存池,最后发现一半以上的数据根本没人访问,还把自己搞得很累。
给每个缓存 key 加上 TTL(生存时间),哪怕你已经在代码里做了主动失效。这个 TTL 是最后的兜底:它限制数据陈旧程度、自动清理被遗弃的 key、在你某个地方忘记失效的时候救你一命。一个没有过期时间的缓存 key 就是一颗定时炸弹,要么导致内存泄漏,要么某天给你返回一份永远过期的数据。唯一可以不设 TTL 的情况是那些真正需要持久化的数据——但说实话,这种数据压根不应该放在缓存层。
key 的命名不是小事。一个清晰、层次分明的命名规则(比如 entity:id:field 这种格式,user:1:profile 就是例子)能让整个 key 空间易于理解,方便批量失效,还能避免 key 冲突。这个规则尽早定下来,之后所有地方都照着来。另外,如果缓存的是结构化数据,在 key 里加一个版本号或 schema 标记。这样当数据结构变了(比如给缓存对象加了一个字段),新老代码不会因为格式不一致出问题——改个版本号就相当于让旧格式的数据自动失效。
你的应用必须能在 Redis 不可用时继续提供服务。cache-aside(旁路缓存)模式下这基本是免费的——缓存未命中时自动回源到数据库。但前提是你的代码要把 Redis 错误当作 miss 来处理,而不是直接崩掉。给缓存读取加个 wrapper,Redis 出错时只记个日志然后走数据库,千万别把错误往上抛变成 500。另外注意另一面:如果 Redis 挂了,所有请求都穿透到数据库,数据库就要承受之前被缓存挡掉的那部分流量。所以缓存挂了有时候会连带着把数据库打挂。限流和 stampede(缓存雪崩)防护在这里就很重要。
下面列几个高频踩雷的点:
maxmemory 和淘汰策略,Redis 会一直吃内存直到被系统 kill。这两个必须设,之前 TTL 那篇有详细说明。你没法优化你看不见的东西。核心指标是 hit rate(命中率):命中次数除以总查询次数。命中率低说明缓存没帮上忙——可能是缓存了错误的数据、TTL 设太短、或者 key 碎片化太严重,这种情况下缓存纯粹是负担。还要监控内存使用量相对于 maxmemory 的比例、eviction 次数(淘汰次数高说明缓存太小或者装的太多)、以及延迟。Redis 通过 INFO 命令暴露这些指标,盯着它们就能知道缓存到底在不在干活。监控这块后续运维模块会深入讲。
如果只记几条:从这个模块里挑最重要的:只缓存读多写少、生成成本高的数据;每个 key 都加 TTL 并打散过期时间;用户相关数据 key 里必须带用户或租户 ID;Redis 挂了走降级而不是崩溃;持续监控命中率。做到这些,能避免大多数缓存相关的事故,而每一条背后都有人付出过代价。
缓存是 Redis 价值最高的功能之一,也是最容易悄悄挖坑的地方。模式给你提供了工具,这些实践帮你避开常见的陷阱。缓存要有目的,过期要有兜底,作用域要谨慎,降级要优雅,最后拿数据说话。
缓存模块到这里就结束了。接下来我们进入 Redis 作为基础设施的使用场景,先聊聊 pub/sub(发布/订阅)消息系统怎么在系统各部分之间做实时通信。