日常知识通
柔彩主题三 · 更轻盈的阅读体验

缓存失效策略击穿防范措施详解

发布时间:2026-01-19 11:40:26 阅读:168 次

缓存为何会“击穿”?

在高并发的系统中,缓存就像家门口的快递柜。大多数人取件时都不用跑远路,直接刷码拿走就行。但如果某天快递柜突然清空,所有人只能去几公里外的站点自提,站点瞬间就挤爆了。系统中的缓存击穿就是这个道理:当某个热点数据在缓存中失效的瞬间,大量请求同时涌向数据库,导致数据库压力骤增,甚至可能宕机。

典型的击穿场景

比如双十一大促期间,某款热门商品的详情页被频繁访问。缓存设置为10分钟过期,第10分01秒时缓存刚好失效,下一秒成千上万的请求同时发现缓存没了,全都冲向数据库查原始数据。数据库还没来得及响应,连接池就被耗尽,服务开始变慢甚至崩溃。

常见缓存失效策略

为了避免这种情况,系统通常会采用一些缓存失效策略。最基础的是设置固定过期时间,但这种方式容易造成“集体失效”。更聪明的做法是引入随机过期时间,比如原本设10分钟,实际在9到11分钟之间随机,让缓存不会在同一时刻大批量失效。

long expireTime = 600 + new Random().nextInt(120); // 600秒基础,加0-120秒随机值
redis.setex("product:123", expireTime, data);

击穿的几种防范手段

除了打散过期时间,还可以通过互斥锁机制来控制访问节奏。当缓存失效时,只允许一个线程去数据库加载数据,其他请求要么等待,要么返回旧数据(如果可用)。

String data = redis.get("product:123");
if (data == null) {
    if (redis.setnx("lock:product:123", "1", 10)) { // 获取锁
        try {
            data = db.query("SELECT * FROM products WHERE id=123");
            redis.setex("product:123", 600, data);
        } finally {
            redis.del("lock:product:123"); // 释放锁
        }
    } else {
        // 没拿到锁,短暂休眠后重试或返回默认值
        Thread.sleep(50);
        data = redis.get("product:123");
    }
}

使用逻辑过期避免物理失效

还有一种思路是不真正让缓存过期,而是把过期时间存在缓存值内部。每次读取时判断逻辑时间是否过期,如果过期就异步更新,但当前请求仍返回旧值。这样既保证了可用性,又避免了雪崩。

多级缓存联动

就像家里既有冰箱又有常温柜,系统也可以设置本地缓存(如Caffeine)+ 分布式缓存(如Redis)的组合。即使Redis失效,本地缓存还能撑一段时间,给后端留出反应时间。本地缓存可以设置更短的过期时间,形成梯度防护。

监控与降级同样重要

再好的策略也挡不住意外。线上系统应实时监控缓存命中率和数据库QPS。一旦发现命中率暴跌,自动触发降级策略,比如关闭非核心功能、返回静态兜底页,防止故障扩散。这就像高速堵车时,交管部门临时开放应急车道分流。