【黑马点评Redis——002商户查询缓存】

1. 商户查询缓存

在这里插入图片描述
在这里插入图片描述

2. 知识储备和课程内容

2.1 什么是缓存

缓存是数据交换的缓冲区,是存贮数据的临时地方,一般读写性能较高。

  • 浏览器缓存
  • 应用层缓存
  • 数据库缓存
  • CPU缓存
  • 磁盘缓存

缓存的作用

  • 降低后端负载
  • 提高读写效率,降低响应时间

缓存的成本

  • 数据的一致性成本
  • 代码维护成本
  • 运维成本

2.2 缓存更新策略

在这里插入图片描述
业务查询:

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

2.2.1 主动更新策略

Cache Aside Pattern(旁路缓存模式)(企业中用的比较多)
  • Cache Aside Pattern
    • 指缓存调用者在更新数据库的同时完成对缓存的更新。(一致性良好,实现难度一般)
  • Read/Write Through Pattern
    • 缓存和数据库集成为一个服务,由服务来保证两者的一致性,对外暴露API接口。调用者调用API,无序知道自己操作的是数据库还是缓存,不关心一致性。(一致性优秀,实现复杂,性能一般)
  • Write Behind Caching Pattern
    • 缓存调用者的CRUD都针对缓存完成,由独立线程异步的将缓存数据写到数据库,实现最终一致(一致性差,性能好,实现复杂)

Cache Aside Pattern基本思想

  1. 当需要获取数据时,首先在缓存中查找数据。
  2. 如果在缓存中找到了数据,则直接返回给客户端。
  3. 如果在缓存中没有找到数据,则从后端存储系统(如数据库)中读取数据,并将数据存储到缓存中。
  4. 在写入数据时,首先更新后端存储系统中的数据,然后让缓存中的数据失效或更新,以便下次读取时从后端存储系统中获取最新数据。

特点

  • 简单直观:模式简单易懂,易于实现。
  • 读性能提升:大部分读操作可以直接从缓存中获取数据,减少了对后端存储系统的访问。
  • 数据一致性:通过手动管理缓存和后端存储系统中的数据一致性,确保数据的准确性。

需要思考的问题!

  • 删除缓存还是更新缓存?
    • 更新缓存:每次更新数据库都更新缓存,无效写操作较多(❎)
    • 删除操作:更新数据库时让缓存失效,查询时再更新缓存(✅)
  • 如何保证缓存与数据库的操作同时成功或失败?
    • 单体系统,将缓存与数据库操作放在一个事务
    • 分布式系统,利用TCC等分布式事务方案
  • 先操作缓存还是先操作数据库?
    • 先删除缓存,在操作数据库
    • 先操作数据库,在删除缓存
先操作缓存还是先操作数据库(重要)?

在这里插入图片描述
相比较而言方案二安全性更高一些
原因:方案二需要满足,线程1查询时缓存恰好失效,且更新数据库的操作间隔要比写入缓存的时间短。(但还是有可能),需要赋予超时剔除作为兜底方案。

2.3 缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这些请求都会打到数据库,给数据库带来巨大的压力。
在这里插入图片描述
在这里插入图片描述

解决方案

  • 缓存空对象
  • 布隆过滤
  • 增强id的复杂度,避免被猜测id规律
  • 做好数据的基础格式校验
  • 加强用户权限校验
  • 做好热点参数的限流

2.4 缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存
    在这里插入图片描述

2.5 缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务比较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
在这里插入图片描述

2.5.1 解决方案1:互斥锁

在这里插入图片描述
存在的问题:需要等待阻塞

在这里插入图片描述
利用setnx来模拟简单的分布式锁。

# 获得锁(一般上会设置有效期)
setnx lock 1
# 删除锁
del lock

2.5.2 解决方案2:逻辑过期

在这里插入图片描述
在这里插入图片描述
基于逻辑过期的方式会存在一段时间内的不一致性,一旦线程完成了缓存重建,就能够得到一致性的结果。

2.5.3 解决方案对比

在这里插入图片描述

2.6 缓存工具封装

基于StringRedisTemplate封装一个缓存工具类,满足下列需求:

  • 方法1: 将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
  • 方法2: 将任意java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
  • 方法3: 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
  • 方法4: 根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
@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 setWithLogicalExpire(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(redisData));}public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;// 1. 从Redis中查询缓存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,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 6. 存在,写入Redisthis.set(key,r,time,unit);// 7. 返回return r;}public <R,ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;// 1. 从Redis中查询缓存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();// 5. 判断是否过期if(expireTime.isAfter(LocalDateTime.now())){// 5.1 未过期,直接返回店铺信息return r;}// 5.2 已过期,需要缓存重建// 6 缓存重建// 6.1 获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2 判断是否获取锁成功if(isLock){// 6.3 成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(()->{// TODO 重建缓存,需要修改过期时间为1800秒try {// 查询数据库R r1 = dbFallback.apply(id);// 写入redisthis.setWithLogicalExpire(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", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void  unlock(String key){stringRedisTemplate.delete(key);}
}

3. 问题汇总

3.1 基于互斥锁的递归是否存在问题?

递归调用 queryWithMutex(id) 可能会导致栈溢出,因为没有任何条件来终止递归。在这种情况下,如果无法获取锁,线程会无限制地尝试递归调用自身,并且每次递归都会消耗一些栈空间,最终导致栈溢出异常。

    public Shop queryWithMutex(Long id){String key = RedisConstants.CACHE_SHOP_KEY + id;// 1. 从Redis中查询缓存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 = RedisConstants.LOCK_SHOP_KEY + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2 判断是否获取成功if (!isLock){// 4.3 失败,则休眠并重试Thread.sleep(50);// TODO 感觉这里代码有问题。建议重新修改return queryWithMutex(id);}// 4.4 成功,根据id查询数据库shop = getById(id);// TODO 模拟重建的延时(正常运行时需要删除)Thread.sleep(200);// 5. 数据库不存在,返回错误if (shop==null){stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 6. 存在,写入RedisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);}catch (Exception e){throw new RuntimeException(e);}finally {// 7.释放获取锁unlock(lockKey);}// 8. 返回return shop;}

修改后的代码如下:采用了循环替代了递归,并设置了最大循环次数。达到最大循环后没有成功即返回null.在并发为200/s的时候平均每个请求需要在循环中执行的次数为7次。
在这里插入图片描述

    public Shop queryWithMutex2(Long id){String key = RedisConstants.CACHE_SHOP_KEY + id;// 从Redis中查询缓存String shopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(shopJson)){return JSONUtil.toBean(shopJson,Shop.class);}// 判断命中的是否是空值,即是否等于空字符串if (shopJson!=null){return null;}// 尝试准备从数据库中获取数据int MAX_RETRY_COUNT = 10;boolean isLock = false;int retryCount = 0;String lockKey = RedisConstants.LOCK_SHOP_KEY + id;Shop shop = null;try{// 4.2 循环重试直至获取锁成功或达到最大重试次数while (!isLock && retryCount < MAX_RETRY_COUNT) {isLock = tryLock(lockKey);if (!isLock) {// 4.3 失败,则休眠并重试Thread.sleep(50);retryCount++;}//休眠结束后尝试从缓存中查询数据shopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(shopJson)){System.out.println("Thread尝试的次数为:"+retryCount);return JSONUtil.toBean(shopJson,Shop.class);}// 否者继续循环获得锁}// 判断是否获取锁成功,或者超过最大重试次数if (!isLock || retryCount == MAX_RETRY_COUNT){return null;}// 获取锁成功,根据id查询数据库shop = getById(id);// TODO 模拟重建的延时(正常运行时需要删除)Thread.sleep(200);// 5. 数据库不存在,返回错误if (shop==null){stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 6. 存在,写入RedisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);}catch (Exception e){throw new RuntimeException(e);}finally {// 7.释放获取锁unlock(lockKey);}// 8. 返回return shop;}

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

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

相关文章

B站无限评论暴力截留协议及教程

B站无限评论暴力截留协议及教程 B站无限评论暴力截留协议及教程&#xff0c;需要抓CK &#xff0c;教程里面有讲如何抓取 网盘自动获取 链接&#xff1a;https://pan.baidu.com/s/1lpzKPim76qettahxvxtjaQ?pwd0b8x 提取码&#xff1a;0b8x

springboot路劲映射

般情况下&#xff0c;使用了页面模板后&#xff0c;用户需要通过控制器才能访问页面。有一些页面需要在控制器中加载数据&#xff0c;然后渲染&#xff0c;才能显示出来;还有一些页面在控制器中不需要加载数据&#xff0c;只是完成简单的跳转&#xff0c;对于这种页面&#xff…

C++:week1:C语言基础

文章目录 (一) C语言概述1.预处理指令&#xff1a;宏定义、宏函数2.生成可执行程序的过程3.进程与虚拟内存空间 (二) 格式化输入输出1.变量及命名2.格式化输入输出、输入输出模型(1)CPU、内存、外部设备的速度矛盾(2)printf(3)scanf 3.代码即注释4.程序出错的原因、调试程序5.其…

提示词优化的自动化探索:Automated Prompt Engineering

编者按&#xff1a; 作者在尝试教授母亲使用 LLM 完成工作任务时&#xff0c;意识到提示词的优化并不像想象中简单。提示词的自动优化对于经验并不丰富的提示词撰写者很有价值&#xff0c;他们没有足够的经验去调整和改进提供给模型的提示词&#xff0c;这引发了对自动化提示词…

C++学习之指针和引用

指针 指针是一个变量&#xff0c;其值为另一个变量的地址&#xff0c;即&#xff0c;内存位置的直接地址。就像其他变量或常量一样&#xff0c;您必须在使用指针存储其他变量地址之前&#xff0c;对其进行声明。指针变量声明的一般形式为&#xff1a; type *var-name; 在这里…

kotlin 编写一个简单的天气预报app (七)使用material design

一、优化思路 对之前的天气预报的app进行了优化&#xff0c;原先的天气预报程序逻辑是这样的。 使用text和button组合了一个输入城市&#xff0c;并请求openweathermap对应数据&#xff0c;并显示的功能。 但是搜索城市的时候&#xff0c;可能会有错误&#xff0c;比如大小写…

steam打不开没反应 steam客户端启动不了一直无响应的解决方法

steam打不开没反应 steam客户端启动不了一直无响应的解决方法 steam这个平台想必各位游戏爱好者们肯定不会陌生&#xff0c;作为全球最大的游戏服务平台&#xff0c;steam不仅为玩家们提供了全面的游戏服务&#xff0c;还经常给玩家们提供各种游戏优惠&#xff0c;并且每年四…

【综述】DSP处理器芯片

文章目录 TI DSP C2000系列 TMS320F28003X 典型应用 开发工具链 参考资料 TI DSP TI C2000系列 控制领域 TI C5000系列 通信领域 TI C6000系列 图像领域 C2000系列 第三代集成了C28浮点DSP内核&#xff0c;采用了65nm工艺&#xff08;上一代180nm&#xff09; 第四代正在…

无人零售与传统便利店的竞争优势

无人零售与传统便利店的竞争优势 成本控制 • 无人零售 显著降低了人力成本&#xff0c;无需支付店员薪资和相关福利&#xff0c;且通过智能化管理减少能源消耗与维护费用&#xff0c;尤其在高租金和高人流区域效益突出。 • 传统便利店 则承担较高的人员开支&#xff0c;…

chrome 查看版本安装路径、cmd命令行启动浏览器

chrome 查看版本安装路径 浏览器输入 chrome://version/cmd命令行启动浏览器 "C:\Program Files\Google\Chrome\Application\chrome.exe" www.baidu.com

恒峰智慧科技—高扬程水泵:解决远距离输水难题的新选择!

在森林消防领域&#xff0c;水泵是一个至关重要的设备。它的主要功能是将水源输送到火灾现场&#xff0c;为消防人员提供足够的水源进行灭火。然而&#xff0c;传统的水泵往往面临着距离限制的问题&#xff0c;这对于远距离输水来说是一个巨大的挑战。幸运的是&#xff0c;高扬…

Jenkins构建触发器-Git hook自动触发构建

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Jenkins是一个开源…

《苍穹外卖》Day10部分知识点记录

一、Spring Task 介绍 Spring Task是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 定位&#xff1a;定时任务框架 作用&#xff1a;定时自动执行某段Java代码 应用场景&#xff1a;只要是需要定时处理的场景都可以使用Spring Task …

2024腾讯游戏安全技术竞赛-机器学习赛道

决赛赛题链接https://gss.tencent.com/competition/2024/doc/2024%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E7%AB%9E%E8%B5%9B-%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0-%E5%86%B3%E8%B5%9B.zip 今年的题目是游戏跨语言恶意内容识别 ,题目比较…

技术速递|利用 Redis 使 AI 驱动的 .NET 应用程序更加一致和智能

作者&#xff1a;Catherine Wang 排版&#xff1a;Alan Wang Redis 是一种流行的内存数据存储&#xff0c;可用于解决构建和扩展智能应用程序的关键挑战。在本文中&#xff0c;你将了解如何使用 Redis 的 Azure 缓存来提高使用 Azure OpenAI 的应用程序的效率。 Redis 的 Azur…

西电超算使用方法-简易版

一、引言 西电超算不错&#xff0c;我很喜欢。本文仅供自己学习使用。 二、环境搭建 搭建环境需要有一些依赖库&#xff0c;但是其实西电超算说明手册并没有写的非常清楚。因此&#xff0c;这次实战演示一下&#xff0c;写一个运行sh文件脚本并提交作业。 1、选择GPU还是CP…

AI赋能分层模式,解构未来,智领风潮

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;AI赋能分…

人脸识别概念解析

目录 1. 概述 2. 人脸检测 3. 人脸跟踪 4. 质量评价 5. 活体检测 6. 特征提取 7. 人脸验证 8. 人脸辨识 1. 概述 人脸识别在我们的生活中随处可见&#xff0c;例如在大楼门禁系统中&#xff0c;它取代了传统的门禁卡或密码&#xff0c;提高了进出的便捷性和安全性。在商…

【Linux】基础指令

文章目录 基础指令1. pwd 指令2. cd 指令3. ls 指令4. touch 指令5. mkdir 指令6. rmdir 和 rm 指令7. man 指令8. cp 指令9. mv 指令10. cat 指令11. more 和 less 指令12. head 和 tail 指令13. date 指令14. cal 指令15. find 指令16. grep 指令18. zip 和 unzip 指令19. ta…

Jenkins - macOS 上安装

文章目录 关于 JenkinsmacOS 上安装 Jenkins方式一&#xff1a;brew方式二&#xff1a;tomcat Jenkins war 关于 Jenkins 官网上下载Jenkins并将其安装到持续集成服务器 https://jenkins.io/download/ macOS 上安装 Jenkins 现在本 macOS 上测试 https://www.jenkins.io/do…