点评项目-7-缓存击穿的两种解决方案、缓存工具类的编写

缓存击穿

在高并发访问的访问中,对于复杂业务 key 的缓存,可能会在缓存生效前打入大量的请求,导致大量的请求打到数据库

解决方案:

1.互斥锁,给缓存的构建过程加上一个锁,当拿到锁时才进行下一步,锁被占用则睡眠一段时间后再拿锁

2.逻辑过期,给缓存加上一个逻辑过期时间,但是在 redis 中过期的数据不会被真正删除,在查询时,如果 key 在逻辑上过期了,则开启一个锁,并把更新 key 的任务交给另一个线程,然后先直接返回旧数据;若某个遇到锁被占用无需等待,直接返回旧数据。

编写缓存工具类,实现代码的复用

@Slf4j
@Component
public class CacheClient {private StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}//存入缓存public void setForString(String key, Object value, long time, TimeUnit unit){String json = JSONUtil.toJsonStr(value);stringRedisTemplate.opsForValue().set(key,json,time,unit);}//删除缓存public void removeKey(String key){stringRedisTemplate.delete(key);}//存入缓存,并设置逻辑日期时间public void setWithLogicalExpire(String key, Object value, long time, TimeUnit unit){RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));//设置过期时间stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData),time,unit);}//防止穿透的查询店铺信息功能缓存public <R,ID> R getById(String preKey, ID id, Class<R> type, Function<ID,R> getById,long time,TimeUnit unit) {String key = preKey + id;//先在 redis 中查询String json = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(json)){//(2024-10-11这个逻辑出现了错误,json 并不是 null,可是封装过去后全是 null,原因:用成了 BeanUtil)return JSONUtil.toBean(json, type);}//判断是否穿透后查询到 ""if(json != null){return null;}//redis 中没查到,在数据库中查询R r = getById.apply(id);if(r == null){setForString(key,"",time,unit);//防击穿,防雪崩return null;}String s = JSONUtil.toJsonStr(r);
//      //(2024-10-11,这一句的逻辑出bug了,找到原因:传参 time 为 0,括号出现了位置错误)stringRedisTemplate.opsForValue().set(key,s,time,unit);return r;}}

 防穿透方法改造后的测试:

    //使用工具类查询(解决缓存穿透版)@Overridepublic Result getById(Long id){long ttl =  (long) (Math.random() * 100)+1;Shop shop = cacheClient.getById("shop:cache:", id, Shop.class, a -> shopMapper.selectById(a),ttl, TimeUnit.MINUTES);if(shop == null){return Result.fail("未查询到该店铺ToT");}return Result.ok(shop);}

基于互斥锁方式改造店铺查询业务:

对于锁的使用,我们可以利用 redis 中的 setnx 命令(只有 key 不存在是操作才成功)来充当锁,需要开启锁时写入一个 setnx ,释放锁时 del 这个 key

//基于互斥锁的方式防止店铺信息查询击穿public <R,ID> R queryShopByLock(String preKey, ID id, Class<R> type, Function<ID,R> getById,long time,TimeUnit unit){String key = preKey + id;//先在 redis 中查询String json = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(json)){//存在,直接返回return JSONUtil.toBean(json, type);}//判断是否穿透后查询到 ""if(json != null){return null;}//redis 中没查到,获取互斥锁String lockKey = "shop:lock:"+id;R r = null;try {boolean getLock = tryToGetLock(lockKey);if(!getLock){//获取锁失败,休眠后重试Thread.sleep(50);queryShopByLock(preKey,id,type,getById,time,unit);}//获取锁成功,完成缓存重建r = getById.apply(id);if(r == null){setForString(key,"",time,unit);return null;}String s = JSONUtil.toJsonStr(r);stringRedisTemplate.opsForValue().set(key,s,time,unit);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {cleanLock(lockKey);}return r;}

 使用 jmeter 进行并发测试:

    @Overridepublic Result getById(Long id){long ttl =  (long) (Math.random() * 100)+1;Shop shop = cacheClient.queryShopByLock("cache:shop:", id, Shop.class, a -> shopMapper.selectById(a), ttl, TimeUnit.MINUTES);if(shop == null){return Result.fail("未查询到该店铺ToT");}return Result.ok(shop);}

所有请求都成功响应 

基于逻辑过期方式改造店铺查询

这种方式一般是通过手动的批量添加需要频繁访问的 key,在不需要使用时再将其批量删除,使用场景有:临时活动相关的请求

这里通过测试方法批量添加 key

    @Testvoid saveKeysWithLogicalExpire(){for(int i=0;i<10;i++){RedisData redisData = new RedisData();Shop shop = shopMapper.selectById(i + 1);redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now());stringRedisTemplate.opsForValue().set("cache:shop:"+shop.getId(), JSONUtil.toJsonStr(redisData));}}//手动删除缓存@Testvoid delKeys(){for(int i=0;i<10;i++){stringRedisTemplate.delete("cache:shop:"+(i+1));}}

维护一个线程池,用于单独处理更新缓存的逻辑

    //线程池,用于逻辑过期执行更新数据库private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

业务逻辑:

    //基于逻辑过期的方式防止店铺信息查询击穿public <R,ID> R queryShopByLogicExpire(String preKey, ID id, Class<R> type , Function<ID,R> getById,long time,TimeUnit unit){String key = preKey + id;//先在 redis 中查询String json = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isBlank(json)){//正常情况一定能查到,没查到,返回空对象return null;}//查到了,将 json 转换为可以使用的类RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(),type);//将Object类型的json转换为指定 type 的类LocalDateTime expireTime = redisData.getExpireTime();//判断是否过期,没过期直接返回数据if(expireTime.isAfter(LocalDateTime.now())){//过期时间在当前时间之后,未过期return r;}//过期了,重建缓存String lockKey = "shop:lock:"+id;boolean getLock = tryToGetLock(lockKey);if(getLock){//成功获取锁,再次判断是否过期boolean isOverTime = judgeLogicalExpire(key);if(!isOverTime){//释放锁cleanLock(lockKey);return r;}//再次判断依然过期,唤醒处理数据库的线程CACHE_REBUILD_EXECUTOR.submit(()->{//需要交给线程执行的逻辑try {R apply = getById.apply(id);this.setWithLogicalExpire(key,apply,time,unit);} catch (Exception e) {e.printStackTrace();}finally {//释放锁cleanLock(lockKey);}});}//返回已过期的数据return r;}//判断是否逻辑过期,true 表示过期public boolean judgeLogicalExpire(String key){String json = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isBlank(json)){return true;}RedisData redisData = JSONUtil.toBean(json, RedisData.class);LocalDateTime expireTime = redisData.getExpireTime();return !expireTime.isAfter(LocalDateTime.now());}//尝试获取锁private boolean tryToGetLock(String key){Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "aaa", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}//释放锁private void cleanLock(String key){stringRedisTemplate.delete(key);}

使用 jmeter 进行并发测试:

    @Overridepublic Result getById(Long id){long ttl =  (long) (Math.random() * 100)+1;Shop shop = cacheClient.queryShopByLogicExpire("cache:shop:", id, Shop.class, a -> shopMapper.selectById(a), ttl, TimeUnit.MINUTES);if(shop == null){return Result.fail("未查询到该店铺ToT");}return Result.ok(shop);}

所有的请求都成功响应 

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

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

相关文章

【Boss直聘-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

线性自抗扰控制(LADRC)系统算法框图

非线性ADRC(NLADRC)详细算法框图和源代码请参考专栏系列文章,常用链接如下: 1、NLADRC自抗扰控制 NLADRC自抗扰控制从Simulink仿真到PLC控制实现_自抗扰控制器 simulink仿真-CSDN博客文章浏览阅读1.6k次,点赞2次,收藏7次。本文介绍了如何将ADRC自抗扰控制算法从Simulink…

牛只行为及种类识别数据集18g牛只数据,适用于多种图像识别,目标检测,区域入侵检测等算法作为数据集。数据集中包括牛只行走,站立,进食,饮水等不同类型的数据

18g牛只数据&#xff0c;适用于多种图像识别&#xff0c;目标检测&#xff0c;区域入侵检测等算法作为数据集。 数据集中包括牛只行走&#xff0c;站立&#xff0c;进食&#xff0c;饮水等不同类型的数据&#xff0c;可以用于行为检测 数据集中包含多种不同种类的牛只&#xff…

【MATLAB源码-第249期】基于matlab的信道量化反馈的预编码仿真,采用均匀量化方式和LDL分解的预编码。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 基于信道量化反馈的预编码技术是当今无线通信系统中的一个核心研究方向。随着多输入多输出&#xff08;MIMO&#xff09;系统的广泛应用&#xff0c;如何有效地利用信道状态信息&#xff08;CSI&#xff09;进行预编码成为提…

用自己的数据集复现YOLOv5

yolov5已经出了很多版本了&#xff0c;这里我以目前最新的版本为例&#xff0c;先在官网下载源码&#xff1a;GitHub - ultralytics/yolov5: YOLOv5 &#x1f680; in PyTorch > ONNX > CoreML > TFLite 然后下载预训练模型&#xff0c;需要哪个就点击哪个模型就行&am…

统一时序预测模型,上下文长度首次扩展至千级别!!!

统一时序预测模型&#xff0c;上下文长度首次扩展至千级别&#xff0c;适用各类数据集&#xff01; 今天给大家介绍一篇清华大学的时间序列预测最新工作&#xff0c;提出了统一的Transformer时序预测模型&#xff0c;能同时处理单变量和多变量时序预测&#xff0c;并将时序预测…

PPT分享:埃森哲-如何利用大数据进行数据挖掘与分析

PPT下载链接见文末~ 在当今信息爆炸的时代&#xff0c;大数据已成为企业决策、科学研究及社会发展的重要驱动力。数据挖掘与分析作为大数据应用的核心环节&#xff0c;能够帮助我们从海量数据中提取有价值的信息和知识。 本文将引导您了解如何利用大数据进行数据挖掘与分析&a…

Android从上帝视角来看PackageManagerService

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 前言 阅读该篇之前&#xff0c;建议先阅读下面的系列文章&#xff1a; Android深入理解包管理–PackageManagerService和它的“小伙伴…

苹果正式宣布:iPhone全面开放近场通信(Near Field Communication,简称NFC)【使用安全元件提供app内NFC数据交换功能】

文章目录 引言I iPhone的NFC功能开发者用户数据交换的体验革新安全与隐私II 知识扩展:近场通信(NFC)技术钱包NFC开关打开读取NFC标签(NFC tags )权限demo引言 2014年iPhone 6开始,苹果首次引入了NFC功能,但最初只允许自家的Apple Pay进行移动支付。慢慢地适配了交通卡,增…

【CSS】纯css3螺旋状loading加载特效

效果图 <div class"ai-loader"><div class"dot"></div><div class"dot"></div><div class"dot"></div><div class"dot"></div><div class"dot">&…

【AI知识点】三种不同架构的大语言模型(LLMs)的区别

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】 在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;预训练语言模型&#xff08;LLMs, Large Language Models&#xff09;通常基于不同的架构&#xff0c;如仅编码器的模型&#xff08;Encoder-only&#xff09;、…

量子计算机的原理与物理实现

量子计算机的原理与物理实现很复杂 指导性原则 首先思考制备一台量子计算机需要些什么&#xff1f; 需要量子比特——二能级量子系统。除了量子计算机需要满足一些物理特性&#xff0c;它还必须要把量子比特绘制到某种初态上&#xff0c;以及测量系统的输出态。 而实验上的挑战…

基于SSM+微信小程序的实验室设备故障报修管理系统2

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的实验室设备故障报修管理系统2实现了管理员&#xff0c;用户&#xff0c;维修员三个角色。 管理员功能有 个人中心&#xff0c;用户管理&#xff0c;维修员管理&#…

[网络基础]——ICMP(互联网控制消息协议)协议介绍

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;网络通信基础TCP/IP专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年10月15日8点23分 在现代网络通信中&#xff0c;互联网控制消息协议&#xff08;ICMP&#xff09;扮演着至关重要的角色…

使用Go语言的gorm框架查询数据库并分页导出到Excel实例

文章目录 基本配置配置文件管理命令行工具: Cobra快速入门基本用法 生成mock数据SQL准备gorm自动生成结构体代码生成mock数据 查询数据导出Excel使用 excelize实现思路完整代码参考 入口文件效果演示分页导出多个Excel文件合并为一个完整的Excel文件 完整代码 基本配置 配置文…

Java项目实战II基于Spring Boot的问卷调查系统的设计与实现(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 基于Spring…

第十三章 RabbitMQ之消息幂等性

目录 一、引言 二、消息幂等解决方案 2.1. 方案一 2.2. 方案二 一、引言 幂等是一个数学概念&#xff0c;用函数表达式来描述是这样的&#xff1a;f(x) f(f(x)) 。在程序开发中&#xff0c;则是指同一个业务&#xff0c;执行一次或多次对业务状态的影响是一致的。有些业务…

【Web——HTML 初阶】网页设计标题

♥HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是构建网页和Web应用的基础语言之一。它不是一种编程语言&#xff0c;而是一种标记语言&#xff0c;用于描述网页的结构和内容。HTML使用标签&#xff08;tags&#xff09;来标记不同类型的…

Jetpack-Room

Room是Android Jetpack中的一个组件&#xff0c;它提供了一个抽象层&#xff0c;帮助开发者在本地数据库&#xff08;如SQLite&#xff09;上进行持久化数据存储。Room通过简化数据库操作&#xff0c;使得数据管理变得更加容易和高效。 Room重要的概念 实体&#xff08;Entit…

[实时计算flink]CREATE DATABASE AS(CDAS)语句

CDAS支持整库级别的表结构和数据的实时同步&#xff0c;还支持表结构变更的同步。本文为您介绍CREATE DATABASE AS&#xff08;CDAS&#xff09;的使用方法&#xff0c;并提供了多种使用场景下的示例。 背景信息 CDAS是CTAS语法的一个语法糖&#xff0c;用于实现整库同步、多…