很多缓存问题看起来是在问“怎么删 Redis”,实际上是在问系统能不能接受短暂不一致,以及出了问题能不能恢复。
我的判断: 缓存不是性能优化的装饰品,而是业务状态的一层投影。只要缓存会影响任务执行、权限判断或分析结果,就必须设计失效、补偿和观测。
先定义一致性要求
不是所有业务都需要强一致。商品详情、排行榜、用户配置、账户余额的要求完全不同。设计缓存前先回答业务能接受什么级别的不一致:
- 允许多长时间的不一致。
- 不一致时用户会看到什么。
- 是否需要主动修复。
写路径比读路径更重要
读缓存很容易,难的是写路径。常见策略包括:
- 更新数据库后删除缓存。
- 延迟双删。
- 写入消息队列异步刷新。
- 直接用版本号或时间戳解决旧值覆盖。
没有绝对正确的策略,只有和业务约束匹配的策略。
降级和观测
缓存层故障时,系统应该知道如何保护数据库。限流、熔断、热点 key 保护和空值缓存都不是锦上添花。
同时要记录缓存命中率、热点 key、穿透次数、Redis 延迟和回源数据库压力。看不见的问题,最后都会变成线上事故。
实际场景:运营配置和分析结果缓存
在广告平台或 BI 平台里,缓存最容易出问题的地方不是“查一次 Redis”,而是配置和结果会被多个入口修改。比如媒体账号授权信息、投放模板、标签版本、分析报表配置,这些数据读多写少,很适合缓存;但它们一旦更新不及时,就会引发很隐蔽的问题:投放任务用了旧 token,批创预览用了旧模板,BI 看板命中了旧结果,用户以为自己配置没生效。
这类场景我会先把缓存分成三类。第一类是纯查询缓存,比如项目列表、媒体账户基础信息,允许短时间不一致,可以设置较短 TTL。第二类是任务依赖缓存,比如媒体 access token、素材映射关系、标签版本日期,这类缓存一旦旧值参与任务执行,就可能造成批量失败,需要更新时主动删除并记录补偿。第三类是重计算结果缓存,比如看板预计算、标签内容结果、分群值列表,它们通常要绑定查询条件、版本和时间窗口,不能只用一个简单 key。
缓存 key 也要带业务语义。比如 bi:tag:value:{projectId}:{tagId}:{version} 比 tag:{id} 更安全,因为它能区分项目、标签和历史版本。广告平台里,媒体账号维度也必须进入 key,否则不同广告主之间可能共享到错误数据。对后端来说,缓存 key 不是字符串拼接,而是业务边界的一部分。
更新策略上,我不会默认迷信“更新数据库后删缓存”就结束。对于运营配置,可以同步删除缓存,失败时记录补偿日志,让定时任务重试删除;对于看板或分析结果,可以写入时带版本号,读取时校验版本,不让旧结果覆盖新结果;对于外部 token,可以在刷新成功后替换缓存,同时保留短时间的失败降级策略,避免媒体接口短暂不可用时全链路中断。
真正上线后还要看指标。缓存命中率突然下降,可能是 key 设计太细,也可能是某个版本参数没有归一化;Redis 慢查询增加,可能是大 value 或批量 key 操作过重;数据库回源压力升高,可能是缓存雪崩或预计算失效。缓存设计的目的不是让代码看起来快,而是在业务变化、任务补跑和外部接口抖动时,系统仍然能解释、能恢复、能定位。
还有一个实践经验是:不要把缓存当成唯一状态源。配置、任务状态、计算结果都应该以数据库或结果表为准,Redis 只是加速层。这样当缓存被误删、过期策略配错、或者需要临时清理时,系统仍然能回源恢复。缓存越重要,越要保留重建路径。