当前位置: 首页
数据库
Redis缓存击穿的用法及说明

Redis缓存击穿的用法及说明

热心网友 时间:2026-04-26
转载

一. 什么是缓存击穿

简单来说,缓存击穿描述的是这样一种场景:一个被高频访问的热点数据(我们称之为热点key),恰好在缓存中过期失效的那一刻,海量的请求瞬间绕过了缓存,直接涌向了后端的数据库。这就像一道原本坚固的堤坝突然出现了一个缺口,所有洪水都从这个缺口冲向下游,结果就是数据库的瞬时压力急剧飙升,甚至可能被直接压垮。

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

这里有个关键点:为什么数据库会扛不住?因为当请求在缓存中查不到数据,转而查询数据库时,这个查询过程往往不是简单的单表查询。它可能涉及多表关联、复杂计算或数据汇总,本身就需要较长的处理时间。在这个“漫长”的查询过程中,成千上万的请求还在源源不断地涌来,数据库的资源(CPU、内存、IO)很快就会被耗尽,导致响应超时、报错,乃至服务彻底宕机。

举个典型的例子:电商平台上的“爆款商品详情页”。这个页面的数据通常会在Redis里缓存1小时。在缓存有效期内,所有用户请求都由Redis轻松处理,数据库高枕无忧。然而,当这个缓存key在晚上8点的流量高峰时刻过期,假设恰好有1000个用户同时点击这个商品,缓存瞬间失效,这1000个请求就会齐刷刷地砸向数据库。数据库很可能在几秒钟内就宣告崩溃。

说到这里,有必要区分两个容易混淆的概念:

  • 缓存穿透:请求的key在缓存和数据库中根本不存在。每次请求都会穿透缓存直达数据库,通常由恶意攻击或错误查询引起。

  • 缓存击穿:请求的key在数据库中是存在的,只是缓存刚好过期了。这是由热点数据的失效时机引发的瞬时风暴。

二. 缓存击穿的核心原因

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

这是击穿的前提。如果某个key访问频率很低,即便它过期了,也只有零星几个请求会打到数据库,掀不起什么风浪。只有当这个key是真正的“热点”,承载着巨大的瞬时流量时,它的失效才会成为一个致命的时间点。

2. 缓存key过期失效

Redis等缓存系统为key设置过期时间是标准操作,目的是及时清理无效数据,释放宝贵的内存空间。但问题在于,如果热点key的过期时间设置得不够合理,或者“运气不好”正好在流量洪峰时过期,击穿的风险就大大增加了。

比如,将爆款商品的缓存时间机械地设为1小时,而没有考虑业务高峰时段,就很容易在晚高峰时“准时”触发击穿。

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

这是最直接的导火索。当缓存失效后,如果系统设计上没有任何缓冲或保护措施——比如限流、降级或请求排队——那么所有请求就会像脱缰野马一样毫无阻拦地冲向数据库。要知道,数据库的并发处理能力与Redis这类内存缓存根本不在一个数量级,这种毫无保护的直接冲击,后果可想而知。

三. 缓存击穿的危害

数据库压力骤增:瞬时的大量并发查询会瞬间拉高数据库的CPU、内存和磁盘IO使用率,导致其响应时间呈指数级增长,所有依赖该数据库的接口都会变慢。

系统响应超时:前端应用在等待数据库响应时,会因为超时而出现加载失败、页面白屏或错误提示,用户体验急剧下降。

数据库宕机:如果请求量完全超出了数据库的承载极限,最坏的情况就是数据库进程崩溃,服务完全不可用。

连锁反应(雪崩):一个核心数据库的宕机,往往会引发连锁反应。即使缓存服务正常,后续请求也无法得到处理。更严重的是,依赖该数据库的其他微服务或业务模块也会随之出现故障,导致整个系统瘫痪。

四. 缓存击穿的解决方案

方案一:互斥锁

这个思路的核心是“串行化”重建过程。当大量请求并发访问时:

  • 命中缓存:皆大欢喜,直接返回数据。
  • 未命中缓存:此时,所有请求不能一窝蜂地去查数据库。它们需要竞争一把“锁”。只有一个请求能成功获取锁,然后由它去查询数据库并重建缓存。其他没抢到锁的请求则短暂休眠后,重新尝试查询缓存(此时缓存很可能已被第一个请求重建好了)。

这种方案保证了数据的强一致性,因为在缓存重建期间,所有请求要么读到旧数据,要么等待新数据。但它的缺点也很明显:性能有损耗。在获取锁和等待缓存重建的这段时间里,大量请求实际上处于阻塞或重试状态,系统吞吐量会受到影响。

Redis缓存击穿的用法及说明

// 1. 注入RedisTemplate(SpringBoot环境)
@Autowired
private RedisTemplate 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 + "对应数据";
}

方案二:逻辑过期

这个方案换了一种思路,它不再依赖Redis的物理过期时间,而是将过期逻辑放在缓存数据的里面。我们封装一个数据结构,里面既包含业务数据,也包含一个逻辑过期时间戳。

当请求到来时:

  • 逻辑未过期:直接返回缓存中的业务数据。
  • 逻辑已过期:这时,系统会尝试获取一把互斥锁。拿到锁的线程,并不会自己同步去查数据库(那样会阻塞当前请求),而是启动一个独立的异步线程去执行查询数据库和更新缓存的任务。而当前请求,以及后续在缓存更新完成前到来的其他请求,都会立刻返回缓存中那个“已逻辑过期”的旧数据。

这种方案的优势在于高可用和高性能。它牺牲了数据的绝对强一致性(在异步更新完成前,用户读到的是稍旧的数据),但保证了服务的永远可用和极速响应。对于许多读多写少、对短暂数据延迟不敏感的场景(如商品描述、文章内容),这是一个非常经典的权衡选择。

Redis缓存击穿的用法及说明

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

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

// 3. 逻辑过期核心方法
public Object getValueByLogicalExpire(String key) {
    // 第一步:查询Redis缓存(获取封装后的CacheData对象)
    CacheData cacheData = (CacheData) 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 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 cacheData = new CacheData<>();
    cacheData.setData(data);
    // 设置逻辑过期时间(30分钟)
    cacheData.setExpireTime(System.currentTimeMillis() + 30 * 60 * 1000);
    // 存入Redis,不设置物理过期时间
    redisTemplate.opsForValue().set(key, cacheData);
}

总结

面对缓存击穿,没有一种“银弹”方案。互斥锁方案通过强制串行化来保证强一致性,适用于对数据实时性要求极高的金融、交易类场景。而逻辑过期方案则通过异步更新和返回旧数据,优先保障了系统的高可用与高性能,更适合资讯、商品详情等容忍短期数据延迟的场景。在实际架构设计中,需要根据具体的业务特性和数据一致性要求,做出最合适的选择。

您可能感兴趣的文章:

  • 解决Redis缓存击穿问题(互斥锁、逻辑过期)
  • Redis缓存雪崩、缓存击穿、缓存穿透详解
  • Redis解决缓存击穿问题的两种方法
  • Redis利用互斥锁解决缓存击穿问题
  • Redis 缓存击穿问题及解决方案
  • Redis解决缓存雪崩、穿透和击穿的问题(Redis使用必看)
来源:https://www.jb51.net/database/362848guy.htm

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
如何使用Java分析Oracle的AWR诊断数据_JDBC读取DBA_HIST视图生成自定义性能分析面板

如何使用Java分析Oracle的AWR诊断数据_JDBC读取DBA_HIST视图生成自定义性能分析面板

ORA-00942错误源于权限不足或连接位置错误:DBA_HIST_视图仅存在于CDB$ROOT,PDB中需用CDB_HIST_;须显式授权SELECT且确认容器上下文。 直接读 DBA_HIST_SQLSTAT 会报 ORA-00942?权限和视图暴露范围是关键 很多朋友在尝试直接查询 DBA_H

时间:2026-04-28 16:25
mysql如何控制DML语句的内存占用_调整ReadRndBufferSize参数

mysql如何控制DML语句的内存占用_调整ReadRndBufferSize参数

MySQL DML内存调优:避开ReadRndBufferSize的误区,抓住真正关键 ReadRndBufferSize 是什么,它真能控制 DML 内存占用吗? 先说一个核心判断:ReadRndBufferSize 这个参数,和 DML 语句的内存占用,完全是两码事。很多朋友在遇到 INSERT

时间:2026-04-28 16:24
Oracle如何实现多表关联删除操作_利用DELETE关联子查询

Oracle如何实现多表关联删除操作_利用DELETE关联子查询

Oracle多表关联删除操作详解:高效实现与避坑指南 在Oracle数据库中进行多表关联删除是一项需要掌握特定技巧的操作。与其他数据库不同,Oracle有其独特的语法要求。核心要点是:Oracle不支持DELETE JOIN标准语法,必须采用EXISTS子查询、IN子查询或结合ROWID的分批删

时间:2026-04-28 16:24
mysql如何利用快照进行备份_基于LVM逻辑卷快照的备份方法

mysql如何利用快照进行备份_基于LVM逻辑卷快照的备份方法

LVM快照不能直接作MySQL备份,因InnoDB内存缓冲与redo log导致文件系统快照不保证数据页一致性;必须先FLUSH TABLES WITH READ LOCK并记录binlog位点,再秒级创建快照,且需挂载后tar导出而非直接拷贝快照LV。 为什么LVM快照不能直接当MySQL备份用

时间:2026-04-28 16:24
Oracle RMAN中CONCURENT操作是什么_理解RMAN并发备份原理

Oracle RMAN中CONCURENT操作是什么_理解RMAN并发备份原理

RMAN并发备份深度解析:核心机制、配置误区与性能瓶颈实战 在Oracle数据库备份与恢复的实践中,许多DBA对RMAN的并发能力存在普遍误解。一个典型的错误是试图寻找类似CONCURRENT这样的命令开关来启用并发。实际上,RMAN的并发能力并非由某个独立的关键字控制,其核心原理在于备份通道(Ch

时间:2026-04-28 16:24
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜
热门教程
更多
  • 游戏攻略
  • 安卓教程
  • 苹果教程
  • 电脑教程