缓存穿透、缓存雪崩、缓存击穿解决方案
什么是缓存
缓存就是数据交换的缓冲区(称作Cache),是存贮数据的临时地方,一般读写性能较高。


添加 redis 缓存

给店铺类型查询业务添加缓存

需求:添加ShopTypeController中的queryTypeList方法,添加查询缓存
缓存更新策略

业务场景:
低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存
高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存

操作缓存和数据库时有三个问题需要考虑:
1.删除缓存还是更新缓存
-
更新缓存:每次更新数据库都更新缓存,无效写操作较多(×)
-
删除缓存:更新数据库时让缓存失效,查询时再更新缓存(✓)
2.如何保证缓存与数据库的操作同时成功或失败
-
单体系统:将缓存与数据库操作放在一个事务
-
分布式系统:利用TCC等分布式事务方案
3.先操作缓存还是先操作数据库
-
先删除缓存,在操作数据库
-
先操作数据库,再删除缓存


读操作:
-
缓存命中则直接返回
-
缓存未命中则查询数据库,并写入缓存,设定超时时间
写操作:
-
先写数据库,然后再删除缓存
-
要确保数据库与缓存操作的原子性
给查询商铺的缓存添加超时剔除和主动更新的策略
修改ShopController的业务逻辑,满足下面的需求
① 根据id查询商铺时,如果缓存未命中,则查询数据库,将数据库数据写入缓存,并设置超时时间
//6.存在,将商铺数据写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
② 根据id修改店铺时,先修改数据库,再修改缓存
@Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if (id==null) {return Result.fail("店铺id不为空");}//1更新数据库updateById(shop);//2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + id);return Result.ok();}
缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
常见的解决方案有两种:

1.缓存空对象
优点:实现简单,维护方便
缺点:额外的内存消耗,可能造成短期的数据不一致
2.布隆过滤
优点:内存占用少,没有多余的key
缺点:实现复杂,存在误判

其他的解决方案
1.增加id的复杂度,避免被猜测id规律
2.做好数据的基础格式校验
3.加强用户权限校验
4.做好热点参数的限流
缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力

解决方案:
1.给不同的key的TTL添加随机值
2.利用redis集群提高服务的可用性
3.给缓存业务添加降级限流策略
4.给业务添加多级缓存
缓存击穿
缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的
key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击

常见的解决方案有两种:
1.互斥锁
2.逻辑过期

优缺点:

基于互斥锁方式解决缓存击穿问题
需求:修改根据id查询商铺的业务,基于互斥锁来解决缓存击穿问题

基于逻辑过期解决缓存击穿问题
需求:修改根据id查询商铺的业务,基于逻辑过期方式来解决缓存击穿问题

缓存工具封装
基于StringRedisTemplate封装一个缓存工具类,满足下列需求:
方法:
1.将任意Java对象序列化为json并存储在String类型的key中,并且设置TTL过期时间
2.将任意Java对象序列化为json并存储在String类型的key中,并且设置逻辑过期时间,用于处理缓存击穿问题
3.根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
4.根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
CacheClient
@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {//设置逻辑过期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));//写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}//解决缓存穿透public <R, ID> R queryWithPassThrough(String kerPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {//1.查询商铺缓存String key = kerPrefix + id;String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isNotBlank(json)) {//3.缓存存在,返回return JSONUtil.toBean(json, type);}//判断命中的是否是空值if (json != null) {//返回一个错误的信息return null;}//4.不存在,根据id查询数据库R r = dbFallback.apply(id);//5.不存在,返回错误if (r == null) {//将空值写入数据库stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}//6.存在,将商铺数据写入redisthis.set(key, r, time, unit);//7.返回return r;}//解决缓存击穿public <R, ID> R queryWithLogicExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallBack, Long time, TimeUnit unit) {//1.查询商铺缓存String key = keyPrefix + id;String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(json)) {//3.缓存存在,返回return null;}//4.命中,需要把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();if (expireTime.isAfter(LocalDateTime.now())) {//5.1.未过期,返回商铺信息return r;}//5.2 已过期,需要缓存重建//6.缓存重建//6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);//6.2判断是否获取锁成功if (isLock) {//6.3成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {//查询数据库R r1 = dbFallBack.apply(id);//写入redisthis.setWithLogicExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unLock(lockKey);}});}//6.4返回过期的商铺信息return r;}private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.MINUTES);return BooleanUtil.isTrue(flag);}private void unLock(String key) {stringRedisTemplate.delete(key);}
}
ShopServiceImpl
@Resourceprivate CacheClient cacheClient;@Overridepublic Result queryById(Long id) {//缓存穿透//Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);//Shop shop = queryWithPassThrough(id);//互斥锁解决缓存击穿//Shop shop = queryWithMutex(id);//逻辑过期解决缓存击穿Shop shop = cacheClient.queryWithLogicExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);//Shop shop = queryWithLogicExpire(id);if (shop == null) {return Result.fail("店铺不存在");}//7.返回return Result.ok(shop);}
视频地址
相关文章:
缓存穿透、缓存雪崩、缓存击穿解决方案
什么是缓存 缓存就是数据交换的缓冲区(称作Cache),是存贮数据的临时地方,一般读写性能较高。 添加 redis 缓存 给店铺类型查询业务添加缓存 需求:添加ShopTypeController中的queryTypeList方法,添加查询缓存 缓存更新…...
web + servlet + jdbc mysql 实现简单的表单管理界面
目录数据库创建数据库连接servlet创建,这里注意一下我的数据库我自己改了一下名字lhx网页html运行文件目录展示首先我们准备好开发使用的工具以及配置 idea2020 tomcat8.5 创建javaweb参考idea编译Tomcat详细步骤 IDEA通过JDBC连接数据库请参考jdbc连接数据库 需要登陆注册界面…...
Maven 国内镜像仓库
镜像仓库目标 当我们未定义任何远程仓库时,使用 Maven 更新依赖时,其会去默认远程仓库中拉取,默认远程仓库 是国外地址,所以在国内访问特别慢,想提升访问速度,需要将国外地址换成国内地址 更换仓库地址的…...
day21 ● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先
二叉搜索树的最小绝对差 二叉搜索树(Binary Search Tree,简称 BST)是一种特殊的二叉树,它的每个节点都满足以下条件: 左子树上所有节点的值均小于该节点的值;右子树上所有节点的值均大于该节点的值&#…...
大学计算机(软件类)专业推荐竞赛 / 证书 官网及赛事相关信息整理
大学计算机专业(软件)推荐竞赛 / 证书 官网及赛事相关信息 一、算法类(丰富简历): 1、ACM国际大学生程序设计竞赛: 官网:https://icpc.global/ 国内:http://icpc.pku.edu.cn/index.htm 报名方式:区域预赛一般每年9-1…...
Metasploit入门到高级【第九章】
预计更新第一章:Metasploit 简介 Metasploit 是什么Metasploit 的历史和发展Metasploit 的组成部分 第二章:Kali Linux 入门 Kali Linux 简介Kali Linux 安装和配置常用命令和工具介绍 第三章:Metasploit 基础 Metasploit 的基本概念Met…...
JDK之8后: 协程? 虚拟线程!!!
特性官方文档: https://openjdk.org/jeps/436 Java协程 近三十年来,Java 开发人员一直依赖线程作为并发服务器应用程序的构建块。每个方法中的每个语句都在线程内执行,并且由于 Java 是多线程的,因此多个执行线程同时发生。线程是Java的并发…...
体验 jeecg
体验 jeecg官网地址事前准备安装升级 node 和 npm 版本验证安装安装 pnpm clidocker 启动 MySQLdocker 启动 redisgit clone 项目启动JAVA项目 jeecg-boot启动Vue3项目 jeecgboot-vue3官网地址 http://www.jeecg.com/ 事前准备 (1) 为了回避Could not find artifact com.mic…...
投稿指南【NO.13】计算机学会CCF推荐期刊和会议分享(人工智能)
前 言国内高等院校研究生及博士毕业条件需要发表高水平期刊或者顶会(清北上交等重点学校毕业要求为至少发一篇顶会),很多同学私信问到一级学会的会议论文怎么找、是什么,比如前段时间放榜的CVPR论文就是人工智能领域的顶会国际会议…...
一份sql笔试
1、 select substr(time,1,10),count(order_id),count(distinct passenger_id) from order where substr(time,1,7)2023-08 group by substr(time,1,10) order by substr(time,1,10);2、 select city_id from (select * from order where substr(time,1,7) 2022-08) t1 left j…...
交换瓶子
交换瓶子 贡献者:programmer_ada 有N个瓶子,编号 1 ~ N,放在架子上。 比如有5个瓶子: 2 1 3 5 4 要求每次拿起2个瓶子,交换它们的位置。 经过若干次后,使得瓶子的序号为: 1 2 3 4 5 对于这么…...
二、Docker安装、启动、卸载、示例
Docker 支持 CentOS 6 及以后的版本,可以直接通过yum进行安装: 使用流程:启动主机 – 启动Docker服务 – 下载容器镜像 – 启动镜像得一个到容器 – 进入容器使用我们想要的程序 主机一般是Linux、Utuban 以下主机系统以CentOS7为例子&#…...
开心档之C++ STL 教程
C STL 教程 目录 C STL 教程 实例 在前面的章节中,我们已经学习了 C 模板的概念。C STL(标准模板库)是一套功能强大的 C 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构…...
Thread 类的基本用法
文章目录一、线程创建1.1 Thread的常见构造方法2.1 创建线程二、线程中断2.1 Thread的几个常见属性2.2 中断线程三、线程等待四、线程休眠五、获取线程实例一、线程创建 1.1 Thread的常见构造方法 方法说明Thread()创建线程对象Thread(Runnable target)使用Runnable对象创建线…...
2023.3.28 天梯赛训练赛补题(病毒溯源 , 龙龙送外卖 , 红色警报)
文章目录1.病毒溯源问题:求树的最长链长度和字典序最小的最长链思路:2.龙龙送外卖思路:3.红色警报:思路:1.病毒溯源 问题:求树的最长链长度和字典序最小的最长链 思路: 一开始用 bfs 做的 &a…...
917. 仅仅反转字母
917. 仅仅反转字母https://leetcode.cn/problems/reverse-only-letters/ 难度简单189 给你一个字符串 s ,根据下述规则反转字符串: 所有非英文字母保留在原有位置。所有英文字母(小写或大写)位置反转。 返回反转后的 s 。 示例…...
Linux-Git
一、总论 1.1 写在前面的话 这已经是我第三遍学Git相关操作了,可以说这个玩意是真的狗,因为确实用不到,不知道下个学期会不会用到,直到现在我刚刚学完,处于知识水平的巅峰,知道Git的具体功能ÿ…...
leetcode:2273. 移除字母异位词后的结果数组(python3解法)
难度:简单 给你一个下标从 0 开始的字符串 words ,其中 words[i] 由小写英文字符组成。 在一步操作中,需要选出任一下标 i ,从 words 中 删除 words[i] 。其中下标 i 需要同时满足下述两个条件: 0 < i < words.l…...
基于Python长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的应用
植被是陆地生态系统中最重要的组分之一,也是对气候变化最敏感的组分,其在全球变化过程中起着重要作用,能够指示自然环境中的大气、水、土壤等成分的变化,其年际和季节性变化可以作为地球气候变化的重要指标。此外,由于…...
4.4---Spring框架之Spring事务(复习版本)
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。 Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的。 S…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
