缓存穿透、缓存雪崩、缓存击穿解决方案
什么是缓存
缓存就是数据交换的缓冲区(称作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…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...