Redis缓存击穿的用法及说明

 更新时间:2026年04月26日 10:24:25   作者:风吹迎面入袖凉  
文章介绍了缓存击穿的概念、核心原因及其对系统的危害,并提出了两种解决方案:互斥锁方案和逻辑过期方案,互斥锁方案能保证数据一致性但性能较低,逻辑过期方案则能提供高可用性和性能

一. 什么是缓存击穿

缓存击穿,指的是某一个热点key(被高频访问的key),在缓存中过期失效的瞬间,大量的请求直接穿透缓存,涌向数据库,导致数据库瞬时压力骤增,甚至被压垮的现象

Tip:当请求打过来时,未在缓存中命中,去数据库查询数据就可能涉及到了很多表和数据库的查询汇总。这种情况就会出现查询数据时间很长,在这期间大量请求涌入数据库,它可能瞬间扛不住,就会出现响应超时、报错,甚至宕机

举个例子:比如电商平台的“爆款商品详情页”,这个商品的key在Redis中缓存了1小时,平时所有请求都走缓存,数据库几乎无压力。但当这个key过期的那一刻,刚好有1000个用户同时访问这个商品,此时缓存中没有数据,所有1000个请求都会直接打向数据库,数据库瞬间扛不住,就会出现响应超时、报错,甚至宕机。

这里要注意区分两个易混淆概念:

  • 缓存穿透:是请求的key本身就不存在于缓存和数据库中,请求一直穿透到数据库。

  • 缓存击穿:是请求的key存在于数据库中,但缓存刚好过期,瞬时请求穿透到数据库。

二. 缓存击穿的核心原因

1. 存在高频访问的热点key

如果key不是热点,即使缓存过期,也只有少量请求穿透到数据库,不会造成太大影响。只有当key被高频访问,才会在缓存失效瞬间对数据库造成巨大压力。

2. 缓存key过期失效

Redis的key都有过期时间,目的是为了释放内存,避免无效数据占用空间。但如果热点key的过期时间设置不合理,或者刚好在请求高峰时过期,就会触发击穿。

比如:把爆款商品的缓存时间设置为1小时,而刚好在晚上8点(用户访问高峰)过期,就极易引发击穿。

3. 缓存与数据库之间无兜底机制

如果缓存失效后,没有任何限流、降级、重试的机制,所有请求会毫无阻拦地冲向数据库,而数据库的并发处理能力远低于Redis,很容易被压垮。

三. 缓存击穿的危害

数据库压力骤增:瞬时大量请求穿透到数据库,导致数据库CPU、内存、IO占用率飙升,响应时间大幅延长。

系统响应超时:数据库处理不过来请求,会导致接口响应超时,前端出现加载失败、白屏等问题。

数据库宕机:如果请求量过大,超过数据库的承载极限,会导致数据库宕机,进而引发整个系统服务不可用。

连锁反应:数据库宕机后,即使缓存恢复,后续请求依然无法正常处理,可能导致服务雪崩,依赖该数据库的其他服务也跟着报错。

四. 缓存击穿的解决方案

方案一:互斥锁

当大量请求过来后走缓存

  • 命中:直接返回缓存数据
  • 未命中:让一个请求获取到锁去查询数据库重建缓存数据,其余未命中且未获取到锁的请求则休眠一段时间后,再次查询缓存。
  • 特点:保证了很强的一致性,但是性能很差(有一段时间内其余未获取锁请求都在空闲等待)

// 1. 注入RedisTemplate(SpringBoot环境)
@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 2. 互斥锁核心方法(获取锁+查询数据库+更新缓存)
public Object getValueByMutexLock(String key) {
    // 第一步:查询缓存
    Object value = redisTemplate.opsForValue().get(key);
    if (value != null) {
        return value; // 缓存存在,直接返回
    }
    
    // 第二步:缓存不存在,尝试获取分布式锁
    String lockKey = "lock:" + key; // 锁key,与业务key绑定,避免锁冲突
    String lockValue = UUID.randomUUID().toString(); // 唯一值,用于释放锁
    boolean isLock = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS); // 锁过期时间3秒(根据数据库查询耗时调整)
    
    if (isLock) {
        try {
            // 第三步:获取锁成功,查询数据库
            value = queryDatabase(key); // 自定义方法,查询数据库数据
            // 第四步:将数据库数据写入缓存,设置过期时间(避免再次击穿)
            redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
            return value;
        } finally {
            // 第五步:释放锁(必须在finally中,避免死锁)
            // 对比value确保是自己的锁,避免误释放他人的锁
            if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
    } else {
        // 第六步:获取锁失败,重试(间隔100ms,避免频繁重试)
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return getValueByMutexLock(key); // 递归重试,也可使用循环
    }
}

// 模拟数据库查询方法
private Object queryDatabase(String key) {
    // 实际业务中替换为真实数据库查询逻辑(如MyBatis查询)
    return "数据库查询到的" + key + "对应数据";
}

方案二:逻辑过期

我们不再设置key的物理过期时间,而是在缓存数据中嵌入一个逻辑过期时间字段。

当大量请求过来后走缓存判断查询字段是否已经过期

  • 未过期:直接返回缓存数据
  • 已过期:获取互斥锁, 此时开启一个新的线程去查询数据库重建缓存,并且将其逻辑时间设置为   当前时间+xxx分钟,释放锁的任务交给该新线程,开启新的线程后,直接返回过期的数据。在重建缓存过程中,其他线程进来查询缓存判断逻辑时间已经过期,且获取锁失败,直接给它返回刚刚缓存中查询出来的过期数据即可。
  • 特点:不能保证数据绝对一致,但是高可用、性能优秀

// 1. 定义缓存数据封装类(封装业务数据+逻辑过期时间)
@Data
public class CacheData<T> {
    // 业务数据
    private T data;
    // 逻辑过期时间(时间戳,单位:毫秒)
    private Long expireTime;
}

// 2. 注入依赖(SpringBoot环境)
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 异步线程池(用于逻辑过期后异步更新缓存)
@Autowired
private ThreadPoolTaskExecutor asyncTaskExecutor;

// 3. 逻辑过期核心方法
public Object getValueByLogicalExpire(String key) {
    // 第一步:查询Redis缓存(获取封装后的CacheData对象)
    CacheData<Object> cacheData = (CacheData<Object>) redisTemplate.opsForValue().get(key);
    if (cacheData == null) {
        // 缓存不存在(首次请求/缓存被手动删除),此处可返回兜底数据或查询数据库
        return queryDatabase(key);
    }
    
    // 第二步:判断逻辑过期时间是否已到
    Long currentTime = System.currentTimeMillis();
    if (currentTime < cacheData.getExpireTime()) {
        // 逻辑未过期,直接返回业务数据
        return cacheData.getData();
    }
    
    // 第三步:逻辑已过期,返回旧数据,同时异步更新缓存
    asyncTaskExecutor.execute(() -> {
        try {
            // 异步查询数据库最新数据
            Object newData = queryDatabase(key);
            // 重新封装CacheData,设置新的逻辑过期时间(如30分钟后)
            CacheData&lt;Object&gt; newCacheData = new CacheData<>();
            newCacheData.setData(newData);
            newCacheData.setExpireTime(System.currentTimeMillis() + 30 * 60 * 1000);
            // 更新Redis缓存(无物理过期时间)
            redisTemplate.opsForValue().set(key, newCacheData);
        } catch (Exception e) {
            // 异常处理(如日志记录),避免异步任务失败导致缓存无法更新
            log.error("逻辑过期缓存更新失败,key:{}", key, e);
        }
    });
    
    // 直接返回旧数据,不阻塞当前请求
    return cacheData.getData();
}

// 模拟数据库查询方法(与互斥锁方案一致)
private Object queryDatabase(String key) {
    return "数据库查询到的" + key + "对应数据";
}

// 4. 初始化缓存(缓存预热,存入带逻辑过期时间的数据)
public void initCache(String key) {
    Object data = queryDatabase(key);
    CacheData&lt;Object&gt; cacheData = new CacheData<>();
    cacheData.setData(data);
    // 设置逻辑过期时间(30分钟)
    cacheData.setExpireTime(System.currentTimeMillis() + 30 * 60 * 1000);
    // 存入Redis,不设置物理过期时间
    redisTemplate.opsForValue().set(key, cacheData);
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Redis3.2开启远程访问详细步骤

    Redis3.2开启远程访问详细步骤

    redis默认只允许本地访问,要使redis可以远程访问可以修改redis.conf
    2018-03-03
  • Redis集群的离线安装步骤及原理详析

    Redis集群的离线安装步骤及原理详析

    这篇文章主要给大家介绍了关于Redis集群的离线安装步骤及原理的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • Redis使用SETNX命令实现分布式锁

    Redis使用SETNX命令实现分布式锁

    分布式锁是一种用于在分布式系统中控制多个节点对共享资源进行访问的机制,本文主要为大家详细介绍了Redis如何使用SETNX命令实现分布式锁,需要的可以参考下
    2025-01-01
  • 详解redis集群的三种方式

    详解redis集群的三种方式

    Redis三种集群方式分别是主从复制,哨兵模式,Cluster集群,这篇文章主要介绍了redis集群的三种方式,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • 基于Redis zSet实现滑动窗口对短信进行防刷限流的问题

    基于Redis zSet实现滑动窗口对短信进行防刷限流的问题

    这篇文章主要介绍了基于Redis zSet实现滑动窗口对短信进行防刷限流,主要针对目前线上短信被脚本恶意盗刷的情况,用Redis实现滑动窗口限流,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-02-02
  • 详解使用Redis SETNX 命令实现分布式锁

    详解使用Redis SETNX 命令实现分布式锁

    本篇文章主要介绍了详解使用Redis SETNX 命令实现分布式锁,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • 推荐几款 Redis 可视化工具(太厉害了)

    推荐几款 Redis 可视化工具(太厉害了)

    这篇文章主要介绍了推荐几款 Redis 可视化工具,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-04-04
  • 基于Redis Streams的实时消息处理实战指南

    基于Redis Streams的实时消息处理实战指南

    这篇文章主要为大家详细介绍了在生产环境中基于 Redis Streams 构建实时消息处理的完整经验,包括技术选型、核心代码示例、踩坑解决和优化方案,希望对大家有所帮助
    2025-07-07
  • Linux下redis密码和远程连接方式

    Linux下redis密码和远程连接方式

    这篇文章主要介绍了Linux下redis密码和远程连接方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Redis如何使用HyperLogLog的实现

    Redis如何使用HyperLogLog的实现

    本文主要介绍了Redis如何使用HyperLogLog的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06

最新评论