电脑学堂
第二套高阶模板 · 更大气的阅读体验

缓存失效策略击穿防范措施:让系统更稳更高效

发布时间:2026-01-19 11:40:50 阅读:126 次

缓存击穿是怎么回事

你有没有遇到过这种情况:网站平时访问挺快,突然某个热门商品页面卡得不行,刷新几次直接504?其实这背后很可能是缓存“击穿”在作怪。简单说,就是某个热点数据的缓存到期了,但大量请求同时冲向数据库,导致数据库瞬间压力飙升,响应变慢甚至崩溃。

常见的缓存失效策略

为了避免缓存集体失效造成雪崩,通常会采用“随机过期时间”。比如原本设置缓存10分钟过期,实际加个几秒的随机值,让不同缓存错峰失效。

但这只能防雪崩,对单个热点key的击穿还是没辙。比如双十一大促时某个爆款商品详情页,缓存一过期,成千上万的请求全压到数据库,扛不住就挂了。

加锁重建缓存

一个有效的办法是:当发现缓存失效时,只允许一个线程去查数据库并重建缓存,其他请求等着用新的缓存结果。可以用分布式锁实现,比如Redis的SETNX。

String getWithLock(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 尝试获取锁
        if (redis.setnx("lock:" + key, "1", 10)) {
            try {
                value = db.query(key);
                redis.setex(key, 300, value); // 重新设置缓存,5分钟
            } finally {
                redis.del("lock:" + key);
            }
        } else {
            // 没拿到锁,短暂休眠后重试
            Thread.sleep(50);
            return getWithLock(key);
        }
    }
    return value;
}

永不过期策略

另一种思路是“逻辑过期”,把过期时间存在缓存值里,而不是依赖Redis本身的TTL。每次读取时判断逻辑时间是否过期,如果过期就异步启动更新任务,但返回旧数据给用户,保证可用性。

public class CacheData {
    String data;
    long expireTime;
}

String getWithLogicalExpire(String key) {
    CacheData cacheData = redis.get(key);
    if (cacheData == null || System.currentTimeMillis() > cacheData.expireTime) {
        // 异步刷新,不影响当前请求返回
        asyncRefresh(key);
    }
    return cacheData != null ? cacheData.data : db.query(key);
}

多级缓存减少穿透风险

除了服务端Redis,还可以在应用本地加一层缓存,比如用Caffeine。这样即使Redis出问题,本地还有热数据顶一阵子。尤其适合访问频率极高的配置类数据。

比如用户登录状态,先查本地缓存,没有再查Redis,还没找到才走数据库。层层拦截,减轻后端压力。

提前预热关键缓存

大促前把热门商品、活动页面的数据提前加载进缓存,避免开抢那一刻集中失效。就像春运抢票前,12306会把热门线路数据提前推送到各级缓存节点。

这种预加载可以结合定时任务,在低峰期自动运行,既保障性能又不增加白天负担。