缓存穿透、击穿和雪崩:别把三种问题混成一件事
缓存能够减少数据库读取,也能让高频页面更快返回。但缓存不是简单加在数据库前面就结束了。请求查不到数据、热点刚好失效、大量键同时过期、缓存服务本身异常,都会让流量重新落到后端。若团队把这些情况统称为“缓存崩了”,往往会套错方案:给不存在的数据加锁,给热点一味延长过期时间,或者只靠增加 Redis 容量来解决批量失效。
更稳妥的做法是先区分现象,再根据数据是否存在、热点程度、过期分布和后端承受能力设计防护。缓存策略应服务于业务正确性和可用性,不能为了提高命中率而返回不该返回的数据,或把过期数据无限期留在系统里。
穿透:先判断请求是否有意义
缓存穿透通常指请求查询的对象在缓存和数据源中都不存在。可能是用户输入错误、爬虫探测、接口被滥用,也可能是数据刚删除后仍有旧链接被访问。每次都直接查数据库,即使单次查询很轻,也会在高并发下形成持续压力。
处理前应确认“不存在”在业务上是什么意思。有些资源确实永远不可能存在,可以较早拒绝;有些对象则可能刚创建、尚未同步,过早判断不存在会影响正常用户。输入参数校验、权限校验和业务状态检查应放在合适位置,不能把所有未知标识一律当作攻击。
对可确认的无效请求,可以使用短期的空结果缓存,减少重复访问下沉。过期时间需要根据数据创建和删除的时效设计,不能无限保存。布隆过滤器等概率结构也可以用于较大规模的已知集合:它能可靠地排除某些不在集合中的键,但“可能存在”并不代表数据库一定有记录,后续仍要正常查询和处理。
击穿:关注的是单个热点重建
缓存击穿与穿透不同,它针对的是本来存在且访问量很高的对象。当一个热点键过期或失效时,很多请求同时发现未命中,并发回源加载同一份数据。后端压力集中在一个对象的重建上,常在活动页、热门内容或高频配置读取时出现。
第一步是识别真正热点,而不是给所有键加复杂锁。可以通过访问量、重建成本和业务重要性判断哪些对象需要特殊处理。普通键偶尔并发回源,可能并不值得引入额外协调;高热点且重建昂贵的键,才需要明确谁负责刷新、其他请求如何等待或降级。
一种策略是在缓存失效后只允许有限请求触发重建,其他请求短暂等待、返回受控的旧值,或给出可理解的稍后重试。锁或单飞机制只能协调重建过程,仍需要设置超时、失败处理和持有者身份,避免重建任务本身卡住时把所有请求长期阻塞。
逻辑过期是另一种思路:物理缓存保留数据,业务根据记录的更新时间决定它是否需要刷新。一个后台任务更新数据,其余请求在有限窗口内可以读取旧值。这适合能够接受短暂陈旧结果的场景,例如内容展示或非实时配置;对余额、库存和强实时状态则不应为了平滑而返回过期结果。
雪崩:看的是一批缓存同时失去作用
缓存雪崩涉及的不是单个热点,而是一大批键在相近时间失效,或缓存服务整体不可用。批量初始化时统一设置过期时间、定时任务集中刷新、发布时误删大范围键,都可能造成前者;单点部署、网络故障和容量耗尽则可能触发后者。
过期时间适度分散能够降低同时回源的概率,但它只是风险缓解,不是绝对保证。随机范围要结合业务过期要求设计,不能让需要准时更新的内容被随意拖延。更重要的是避免把大量关键键绑定到同一个刷新时刻,并对批量操作设置范围检查和回滚路径。
缓存服务故障时,后端需要有明确的保护策略。数据库连接池、并发预算、限流和降级可以防止所有请求一起冲向数据源;有些非关键功能可以暂时关闭或使用有限的本地结果。策略应区分接口优先级,不能让一项低价值查询挤占核心交易所需资源。
多副本、合理的客户端超时和故障切换有助于提高缓存服务可用性,但也要测试切换期间的行为。客户端无限重试或同步等待,可能在服务恢复前先把应用线程耗尽。
缓存数据的正确性要有边界
缓存中保存什么、何时更新、删除后怎样失效,都必须与数据写入路径协调。仅在读取端添加缓存,不处理写入后的更新或失效,会让用户长期看到旧数据。写后删除、更新缓存、事件驱动刷新等方式各有适用条件,需要结合并发写入和失败恢复设计。
不要把缓存当作权限判断的最终来源。个性化数据、账户状态和敏感信息需要按用户与权限隔离;缓存键或内容范围不清楚时,可能把一个用户的结果返回给另一个用户。命中率再高也无法弥补这种错误。
观测指标也应关注正确性:命中与未命中、回源时间、重建失败、空结果比例、过期数据读取和缓存服务错误,都能帮助判断策略是否在按预期工作。只有 QPS 和内存使用量,不足以说明缓存真的保护了后端。
用真实失败场景验证策略
上线前,可以在受控环境模拟几种关键情况:持续请求不存在对象、热点键同时过期、刷新任务失败、大批键需要重新加载、缓存节点短暂不可用。观察数据库是否仍在可承受范围内,用户是否得到适当反馈,数据是否会错误地长期陈旧。
发生异常后,团队应知道先保护什么、谁有权关闭某项缓存策略、如何恢复和核对数据。运行手册不必写得很长,但应避免现场临时执行大范围删除或无界重试。
缓存穿透、击穿和雪崩的共同点是都会增加回源压力,原因和处理方式却不同。先识别对象是否存在、是否是热点、是否会批量失效,再选择短期空值、受控重建、过期分散和后端保护,缓存才能在压力到来时真正发挥作用。