Redis缓存击穿的用法及说明
一. 什么是缓存击穿
简单来说,缓存击穿描述的是这样一种场景:一个被高频访问的热点数据(我们称之为热点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使用率,导致其响应时间呈指数级增长,所有依赖该数据库的接口都会变慢。
系统响应超时:前端应用在等待数据库响应时,会因为超时而出现加载失败、页面白屏或错误提示,用户体验急剧下降。
数据库宕机:如果请求量完全超出了数据库的承载极限,最坏的情况就是数据库进程崩溃,服务完全不可用。
连锁反应(雪崩):一个核心数据库的宕机,往往会引发连锁反应。即使缓存服务正常,后续请求也无法得到处理。更严重的是,依赖该数据库的其他微服务或业务模块也会随之出现故障,导致整个系统瘫痪。
四. 缓存击穿的解决方案
方案一:互斥锁
这个思路的核心是“串行化”重建过程。当大量请求并发访问时:
- 命中缓存:皆大欢喜,直接返回数据。
- 未命中缓存:此时,所有请求不能一窝蜂地去查数据库。它们需要竞争一把“锁”。只有一个请求能成功获取锁,然后由它去查询数据库并重建缓存。其他没抢到锁的请求则短暂休眠后,重新尝试查询缓存(此时缓存很可能已被第一个请求重建好了)。
这种方案保证了数据的强一致性,因为在缓存重建期间,所有请求要么读到旧数据,要么等待新数据。但它的缺点也很明显:性能有损耗。在获取锁和等待缓存重建的这段时间里,大量请求实际上处于阻塞或重试状态,系统吞吐量会受到影响。

// 1. 注入RedisTemplate(SpringBoot环境) @Autowired private RedisTemplateredisTemplate; // 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的物理过期时间,而是将过期逻辑放在缓存数据的值里面。我们封装一个数据结构,里面既包含业务数据,也包含一个逻辑过期时间戳。
当请求到来时:
- 逻辑未过期:直接返回缓存中的业务数据。
- 逻辑已过期:这时,系统会尝试获取一把互斥锁。拿到锁的线程,并不会自己同步去查数据库(那样会阻塞当前请求),而是启动一个独立的异步线程去执行查询数据库和更新缓存的任务。而当前请求,以及后续在缓存更新完成前到来的其他请求,都会立刻返回缓存中那个“已逻辑过期”的旧数据。
这种方案的优势在于高可用和高性能。它牺牲了数据的绝对强一致性(在异步更新完成前,用户读到的是稍旧的数据),但保证了服务的永远可用和极速响应。对于许多读多写少、对短暂数据延迟不敏感的场景(如商品描述、文章内容),这是一个非常经典的权衡选择。

// 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
总结
面对缓存击穿,没有一种“银弹”方案。互斥锁方案通过强制串行化来保证强一致性,适用于对数据实时性要求极高的金融、交易类场景。而逻辑过期方案则通过异步更新和返回旧数据,优先保障了系统的高可用与高性能,更适合资讯、商品详情等容忍短期数据延迟的场景。在实际架构设计中,需要根据具体的业务特性和数据一致性要求,做出最合适的选择。
您可能感兴趣的文章:
- 解决Redis缓存击穿问题(互斥锁、逻辑过期)
- Redis缓存雪崩、缓存击穿、缓存穿透详解
- Redis解决缓存击穿问题的两种方法
- Redis利用互斥锁解决缓存击穿问题
- Redis 缓存击穿问题及解决方案
- Redis解决缓存雪崩、穿透和击穿的问题(Redis使用必看)
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
如何使用Java分析Oracle的AWR诊断数据_JDBC读取DBA_HIST视图生成自定义性能分析面板
ORA-00942错误源于权限不足或连接位置错误:DBA_HIST_视图仅存在于CDB$ROOT,PDB中需用CDB_HIST_;须显式授权SELECT且确认容器上下文。 直接读 DBA_HIST_SQLSTAT 会报 ORA-00942?权限和视图暴露范围是关键 很多朋友在尝试直接查询 DBA_H
mysql如何控制DML语句的内存占用_调整ReadRndBufferSize参数
MySQL DML内存调优:避开ReadRndBufferSize的误区,抓住真正关键 ReadRndBufferSize 是什么,它真能控制 DML 内存占用吗? 先说一个核心判断:ReadRndBufferSize 这个参数,和 DML 语句的内存占用,完全是两码事。很多朋友在遇到 INSERT
Oracle如何实现多表关联删除操作_利用DELETE关联子查询
Oracle多表关联删除操作详解:高效实现与避坑指南 在Oracle数据库中进行多表关联删除是一项需要掌握特定技巧的操作。与其他数据库不同,Oracle有其独特的语法要求。核心要点是:Oracle不支持DELETE JOIN标准语法,必须采用EXISTS子查询、IN子查询或结合ROWID的分批删
mysql如何利用快照进行备份_基于LVM逻辑卷快照的备份方法
LVM快照不能直接作MySQL备份,因InnoDB内存缓冲与redo log导致文件系统快照不保证数据页一致性;必须先FLUSH TABLES WITH READ LOCK并记录binlog位点,再秒级创建快照,且需挂载后tar导出而非直接拷贝快照LV。 为什么LVM快照不能直接当MySQL备份用
Oracle RMAN中CONCURENT操作是什么_理解RMAN并发备份原理
RMAN并发备份深度解析:核心机制、配置误区与性能瓶颈实战 在Oracle数据库备份与恢复的实践中,许多DBA对RMAN的并发能力存在普遍误解。一个典型的错误是试图寻找类似CONCURRENT这样的命令开关来启用并发。实际上,RMAN的并发能力并非由某个独立的关键字控制,其核心原理在于备份通道(Ch
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

