Redis之缓存击穿

Redis之缓存击穿

在这里插入图片描述

文章目录

  • Redis之缓存击穿
    • 一、什么是缓存击穿
    • 二、缓存击穿常见解决方案
      • 1. 互斥锁(Mutex Lock)
      • 2. 永不过期 + 后台刷新
      • 3. 逻辑过期(异步更新)
    • 三、案例
      • 1.基于互斥锁解决缓存击穿
      • 2.基于逻辑过期解决缓存击穿
    • 四、注意事项
      • 1.​ 锁的选择
      • 2. 递归重试风险
      • 3. 锁超时时间
      • ​4. 缓存过期时间随机化

一、什么是缓存击穿

缓存击穿(Cache Breakdown)是指某个热点 Key 在缓存中过期后,大量并发请求同时绕过缓存直接访问数据库,导致数据库压力骤增的现象。

通常发生在以下场景:

  • 某个 Key 是高频访问的「热点数据」。
  • Key 的缓存过期时间到期,此时大并发请求同时到达。
  • 缓存失效瞬间,所有请求都去查询数据库并重建缓存。
线程1 线程2 线程3 线程4 1.查询缓存,未命中 2.查询数据库,重建缓存数据 3.查询缓存,未命中 4.查询数据库,重建缓存数据 5.查询缓存,未命中 7.查询缓存,未命中 6.查询数据库,重建缓存数据 数据查询耗时等待200ms 8.查询数据库,重建缓存数据 9.写入缓存 线程1 线程2 线程3 线程4

二、缓存击穿常见解决方案

1. 互斥锁(Mutex Lock)

  • 原理:当缓存失效时,只允许一个线程去加载数据,其他线程等待缓存更新完成后再读取缓存。
    • 优点: 确保数据强一致性
    • 缺点:线程需要等待,可能成为性能瓶颈(锁竞争)

  • 流程图
缓存命中
缓存未命中
成功
失败
请求缓存
返回数据
获取互斥锁
查询数据库
更新缓存
释放锁
等待并重试
  • 伪代码
public Object getData(String key) {Object data = cache.get(key);if (data != null) return data;// 加锁(如Redis的SETNX)String lockKey = "lock:" + key;if (redis.setnx(lockKey, "1", 10)) { // 10秒锁超时try {// 二次检查缓存(防止锁竞争期间其他线程已加载)data = cache.get(key);if (data != null) return data;data = db.query(key);cache.set(key, data);} finally {redis.del(lockKey); // 释放锁}} else {// 等待重试Thread.sleep(100);return getData(key);}return data;
}

2. 永不过期 + 后台刷新

  • 原理:为缓存设置永不过期时间,同时通过后台线程主动更新缓存。
    • 优点:无阻塞,适合对一致性要求低场景
    • 缺点:数据可能短暂陈旧

  • 流程图
缓存命中
缓存未命中
请求缓存
返回数据
查询数据库并更新缓存
后台定时任务
  • 伪代码
// 初始化时设置缓存永不过期
cache.set("hot_key", data)// 后台线程定期更新
public void backgroundRefresh() {while(true) {Thread.sleep(5 * 60 * 1000)  // 每5分钟更新一次newData = db.query("hot_key")cache.set("hot_key", newData)}
}

3. 逻辑过期(异步更新)

  • 原理:在缓存中存储数据的逻辑过期时间,即使缓存未物理过期,若逻辑过期则异步更新。
    • 优点:无阻塞,兼容性强
    • 缺点:不保证一致性,实现复杂度较高

  • 时序图
线程1 线程2 线程3 线程4 1.查询缓存 发现逻辑时间已过期 2.获取互斥锁成功 3.开启新线程 (异步操作) 1.查询缓存 发现逻辑时间已过期 2.获取互斥锁失败 3.返回过期数据 4.返回过期数据 1.查询数据库 重建缓存数据 2.写入缓存 重置逻辑过期时间 1.命中缓存,并且没有过期 2.返回缓存数据 3.释放锁 线程1 线程2 线程3 线程4
  • 伪代码

缓存条目类

@Data
public class CacheEntry {private final String data;private final long expireTime;
}
    // 获取当前时间戳(毫秒)private static long now() {return System.currentTimeMillis();}public static String getData(String key) {CacheEntry entry = cache.get(key);// 缓存未命中if (entry == null) {String data = Database.query(key);long expireTime = now() + 300_000; // 5分钟过期(300秒 * 1000)cache.put(key, new CacheEntry(data, expireTime));return data;}// 检查逻辑过期if (entry.getExpireTime() < now()) {// 启动异步更新线程new Thread(() -> asyncUpdate(key)).start();}// 返回过期数据return entry.getData();}private static void asyncUpdate(String key) {String newData = Database.query(key);long newExpireTime = now() + 300_000;cache.put(key, new CacheEntry(newData, newExpireTime));}

三、案例

1.基于互斥锁解决缓存击穿

命中
未命中
开始
提交商铺id
从Redis查询商铺缓存
判断缓存是否命中
返回数据
结束
尝试获取互斥锁
判断是否获取锁
根据id查询数据库
休眠一段时间
将商铺数据写入Redis
释放互斥锁
public Shop queryWithMutex(Long id) {// 1.从redis查询商铺缓存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}// 判断命中的是否是空值if(shopJson != null) {// 返回错误信息,解决缓存穿透问题return null;}// 4.实现缓存重建// 4.1 获取互斥锁String lockKey = LOCK_SHOP_KEY + id;Shop shop;try {boolean isLock = tryLock(lockKey);// 4.2 判断是否获取成功if (!isLock) {// 4.3 失败,则休眠并重试Thread.sleep(50);return queryWithMutex(id);}// 4.4 成功,根据id查询数据库,返回数据shop = getById(id);if (shop == null) {// 5.数据库不存在,将空字串写入Redis,设置过期时间,解决缓存穿透问题stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息,解决缓存穿透问题return null;}// 6.存在,写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 7.释放锁unLock(lockKey);}return shop;}

2.基于逻辑过期解决缓存击穿

未命中
命中
未过期
过期
开始
提交商铺id
从Redis查询商铺缓存
判断缓存是否命中
返回空
结束
判断缓存是否过期
返回商铺信息
尝试获取互斥锁
判断是否获取锁
开启独立线程
根据id查询数据库
将商铺数据写入Redis,并设置逻辑过期时间
释放互斥锁
public Shop queryWithLogicalExpire(Long id) {// 1.从redis查询商铺缓存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(shopJson)) {// 3.不存在,直接返回nullreturn null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);JSONObject data = (JSONObject) redisData.getData();Shop shop = JSONUtil.toBean(data, Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {// 5.1 未过期,直接返回数据return shop;}// 5.2 过期,需要缓存重建// 6. 缓存重建String lockKey = LOCK_SHOP_KEY + id;// 6.1 获取互斥锁boolean isLock = tryLock(lockKey);// 6.2 判断是否获取锁成功if (isLock) {// 6.3 成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);} finally {// 6.5 释放锁unLock(lockKey);}});}// 6.4 返回过期的商铺信息return shop;}

四、注意事项

1.​ 锁的选择

  • 单机环境用 ReentrantLocksynchronized
  • 分布式环境需用 Redis 分布式锁(如 Redisson 的 RLock)。

2. 递归重试风险

  • 示例中递归调用,可能导致栈溢出,实际生产环境应改用循环重试。

3. 锁超时时间

  • 分布式锁需设置合理超时时间(如 300ms),防止死锁。

​4. 缓存过期时间随机化

  • 可对缓存 TTL 添加随机值(如 300 + rand.nextInt(100)),避免缓存雪崩。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 中使用 Netty

2025/4/15 向 一、什么是Netty Netty 是 Java 中一个非常高性能的网络通信框架&#xff0c;用来开发服务器和客户端程序&#xff0c;主要用于处理 TCP/UDP 的网络连接&#xff0c;比如&#xff1a; 聊天服务 实时推送 高并发网络通信&#xff08;比如游戏、IoT、金融系统&a…

【QT】 QT定时器的使用

QT定时器的使用 1. QTimer介绍&#xff08;1&#xff09;QTimer的使用方法步骤示例代码1&#xff1a;定时器的启动和关闭现象&#xff1a;示例代码2&#xff1a;定时器每隔1s在标签上切换图片现象&#xff1a; (2)实际开发的作用 2.日期 QDate(1)主要方法 3.时间 QTime(1)主要方…

排序算法详细介绍对比及备考建议

文章目录 排序算法对比基本概要 算法逐一介绍1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 选择排序&#xff08;Selection Sort&#xff09;3. 插入排序&#xff08;Insertion Sort&#xff09;&#x1f31f;&#x1f31f;4. 希尔排序&#xff08;Shell Sort&#xff09…

Docker华为云创建私人镜像仓库

Docker华为云创建私人镜像仓库 在华为云官网的 产品 中搜索 容器镜像服务 &#xff1a; 或者在其他页面的搜索栏中搜索 容器镜像服务 &#xff1a; 进入到页面后&#xff0c;点击 创建组织 &#xff08;华为云的镜像仓库称为组织&#xff09;&#xff1a; 设置组织名字后&…

微信小程序-自定义toast

微信小程序-自定义toast 微信小程序原生的toast最多能显示两行文字。方案1:方案2 微信小程序原生的toast最多能显示两行文字。 有时候并不能满足业务需求。所以我们需要使用第三方或者自定义。 方案1: 第三方vant-toast 微信小程序下载引入第三方vant之后。 在需要使用的页面…

安卓手游逆向

一、环境安装 1.1、安装Java环境 1.2、安装SDK环境 1.3、安装NDK环境 二、APK 2.1、文件结构 2.2、打包流程 2.3、安装流程 应用安装涉及目录: system/app ----->系统自带的应用程序,获得adb root权限才能删除。 data/app ------->用户程序安装的目录,安装…

VSCode Continue 扩展踩坑记录

Trae 是一款很优秀的 AI 开发工具&#xff0c;但目前支持的平台还较少&#xff0c;比如不支持 Win7&#xff0c;不支持 Linux&#xff0c;为了在这些平台上进行开发&#xff0c;我需要寻找一个替代品。经过网上搜索&#xff0c;选择了 VSCode Continue 扩展&#xff0c;但在使…

Elasticsearch:AI 助理 - 从通才到专才

作者&#xff1a;来自 Elastic Thorben Jndling 在 AI 世界中&#xff0c;关于构建针对特定领域定制的大型语言模型&#xff08;large language models - LLM&#xff09;的话题备受关注 —— 不论是为了更好的安全性、上下文理解、专业能力&#xff0c;还是更高的准确率。这个…

【ARM】MDK烧录提示Error:failed to execute‘ ‘

1、 文档目标 解决在烧录程序的时候&#xff0c;因为选择了错误的烧录方式导致下载失败的情况。 2、 问题场景 在烧录程序的时候出现了提示&#xff1a;“Error&#xff1a;failed to execute ’ ”&#xff08;如图2-1&#xff09;。检测Target->Debug配置发现没有问题&a…

系统分析师(六)-- 计算机网络

概述 TCP/IP 协议族 DNS DHCP 网络规划与设计 逻辑网络设计 物理网络设计 题目 层次化网络设计 网络冗余设计 综合布线系统 IP地址 网络接入技术 其他网络技术应用 物联网

优化运营、降低成本、提高服务质量的智慧物流开源了

智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本可通过边缘计算技术…

从One-Hot到TF-IDF:NLP词向量演进解析与业务实战指南(一)

从One-Hot到TF-IDF&#xff1a;词向量演进之路 开场白&#xff1a; 想象一下&#xff0c;你试图用Excel表格分析《红楼梦》的情感倾向——每个字词都是孤立的单元格&#xff0c;计算机看到的只有冰冷的0和1&#xff0c;而“黛玉葬花”的凄美意境却消失得无影无踪。这就是NLP工…

2. kubernetes操作概览

以下是 Kubernetes 的核心操作概览&#xff0c;涵盖常用命令、资源管理和典型场景的操作流程&#xff1a; 1. 核心操作工具 (1) kubectl 命令行工具 Kubernetes 的所有操作均通过 kubectl 实现&#xff0c;常用命令如下&#xff1a; 操作类型命令示例作用说明查看资源状态ku…

从Ampere到Hopper:GPU架构演进对AI模型训练的颠覆性影响

一、GPU架构演进的底层逻辑 AI大模型训练效率的提升始终与GPU架构的迭代深度绑定。从Ampere到Hopper的演进路径中&#xff0c;英伟达通过‌张量核心升级‌、‌显存架构优化‌、‌计算范式革新‌三大技术路线&#xff0c;将LLM&#xff08;大语言模型&#xff09;训练效率提升至…

p2p的发展

PCDN&#xff08;P2P内容分发网络&#xff09;行业目前处于快速发展阶段&#xff0c;面临机遇与挑战并存的局面。 一、发展机遇 技术融合推动 边缘计算与5G普及&#xff1a;5G的高带宽、低延迟特性与边缘计算技术结合&#xff0c;显著提升PCDN性能&#xff0c;降低延迟&#x…

计算机视觉与深度学习 | 视觉里程计(Visual Odometry, VO)学习思路总结

视觉里程计(Visual Odometry, VO)学习思路总结 视觉里程计(VO)是通过摄像头捕获的图像序列估计相机运动轨迹的技术,广泛应用于机器人、自动驾驶和增强现实等领域。以下是一个系统的学习路径,涵盖基础理论、核心算法、工具及实践建议:一、基础理论与数学准备 核心数学工具…

Ubuntu 24.04 中文输入法安装

搜狗输入法&#xff0c;在Ubuntu 24.04上使用失败&#xff0c;安装教程如下 https://shurufa.sogou.com/linux/guide 出现问题的情况&#xff0c;是这个帖子里描述的&#xff1a; https://forum.ubuntu.org.cn/viewtopic.php?t493893 后面通过google拼音输入法解决了&#x…

阿里云 MSE Nacos 发布全新“安全防护”模块,简化安全配置,提升数据保护

作者&#xff1a;张文浩 阿里云在其微服务引擎&#xff08;MSE&#xff09;注册配置中心 Nacos 上正式推出全新“安全防护”功能模块&#xff0c;旨在帮助企业用户有效管理安全状态和降低开启安全相关功能的学习成本&#xff0c;提升微服务架构的安全性。首期推出的“安全防护…

C#核心(23)StringBuilder

前言 我们先前已经了解了String的一些基本规则和常见的用法,今天就来讲一下和string有所区别的StringBulider。 在 C# 中,StringBuilder 类是一个非常有用的工具,特别是在需要频繁修改字符串时。与 String 类型不同,StringBuilder 类提供了一种动态字符串,可以在不创建新…

活动图与流程图的区别与联系:深入理解两种建模工具

目录 前言1. 活动图概述1.1 活动图的定义1.2 活动图的基本构成要素1.3 活动图的应用场景 2. 流程图概述2.1 流程图的定义2.2 流程图的基本构成要素2.3 流程图的应用场景 3. 活动图与流程图的联系4. 活动图与流程图的区别4.1 所属体系不同4.2 表达能力差异4.3 使用目的与语境4.4…