缓存最容易带来一种错觉:只要把数据放进 Redis,接口就会变快,系统也会更稳定。实际情况是,缓存增加了一份需要同步、过期和解释的状态。它应该减少重复工作,而不是替数据库承担最终事实。
旁路缓存是最容易开始的模式:先查缓存,未命中再查数据库,成功后写回。代码很短,边界却不少。空结果是否缓存,缓存键是否包含租户和版本,序列化失败怎么办,数据库更新以后旧值怎样失效,都要在业务里写清楚。一个 get 和一个 set 不能替代一致性策略。
缓存穿透指的是大量请求一个根本不存在的键。缓存空值和布隆过滤器都能减少数据库压力,但它们有不同的误判和过期成本。空值 TTL 太长会阻止后来出现的新数据,太短又挡不住重复请求;布隆过滤器需要在写入新数据时同步更新。选方案以前先看查询模式,不要把名词当成答案。
缓存击穿更像一个热点键的瞬间失效。互斥锁、逻辑过期和旧值兜底都可以降低并发回源,但每个方案都会增加状态和复杂度。对于不重要的列表,返回稍旧的内容可能比让所有请求等待更合理;对于权限和余额,旧值则可能带来错误决定,宁愿直接走数据库并限制流量。
缓存雪崩通常是一批键在同一时刻过期。给 TTL 加一点抖动、分散预热时间、限制回源并发,都能降低峰值,但前提是数据库和连接池本身有容量边界。否则只是把一次尖峰变成更长的拥堵。
最重要的退路是可观测性。命中率只能说明缓存被访问的结果,不能说明业务正确;还要记录回源耗时、锁等待、序列化错误和失效原因。清空缓存也应该是一个明确动作,知道清空后会触发多少数据库请求,并在高峰期避免随手执行。
缓存的价值在于让系统有更多选择:命中时快一点,失效时仍能完成,错误时能回到源数据。把退路写清楚以后,缓存才是加速器;否则它只是另一份会在很晚突然过期的数据库。
还有一个常被忽略的选项:不缓存。低频、强一致或查询本来就很便宜的数据,直接读源可能更简单。缓存的成本不只是一点内存,还包括失效通知、监控、排障和迁移;把这些成本算进去,才能知道它是否真的值得。
缓存设计还要写清楚谁负责失效。更新数据库以后,哪些键必须删除,哪些键可以等待 TTL,读到旧值时是否允许短暂降级,都应在业务边界里明确。只写一个“加缓存”开关,无法解释数据为什么会旧。
上线前我会先估算源站、连接池和缓存的容量,再决定预热、限流和抖动策略。缓存命中率很高但数据库连接已经耗尽,依然不是一个健康的系统;指标应该一起看,而不是只盯着一个百分比。
缓存的退路可以先用一张很朴素的流程图说清楚。真正需要补充的,是每个箭头上的过期、并发和错误策略。