当前位置: 首页 > news >正文

学校网站建设招标公告深圳营销型网站建设设计公司

学校网站建设招标公告,深圳营销型网站建设设计公司,怎么知道网站的空间是谁做的,网站建立方案2022黑马Redis跟学笔记.实战篇 三4.2.商家查询的缓存功能4.3.1.认识缓存4.3.1.1.什么是缓存4.3.1.2.缓存的作用1.为什么要使用缓存2.如何使用缓存3. 添加商户缓存4. 缓存模型和思路4.3.1.3.缓存的成本4.3.2.添加redis缓存4.3.3.缓存更新策略4.3.3.1.三种策略(1).内存淘汰:Redis… 2022黑马Redis跟学笔记.实战篇 三4.2.商家查询的缓存功能4.3.1.认识缓存4.3.1.1.什么是缓存4.3.1.2.缓存的作用1.为什么要使用缓存2.如何使用缓存3. 添加商户缓存4. 缓存模型和思路4.3.1.3.缓存的成本4.3.2.添加redis缓存4.3.3.缓存更新策略4.3.3.1.三种策略(1).内存淘汰:Redis自带的内存淘汰机制(2).过期淘汰:利用expire命令给数据设置过期时间(3).主动更新:主动完成数据库与缓存的同时更新4.3.3.2.策略选择4.3.3.2.1.低一致性需求4.3.3.3.2.高一致性需求4.3.3.3.主动更新的方案4.3.3.3.1.Cache Aside1.缓存调用者在更新数据库的同时完成对完成的更新4.3.3.3.2.Read/Write Through4.3.3.3.3.Write Back4.3.3.4.Cache Aside的模式选择4.3.3.4.1.更新缓存还是删除缓存4.3.3.4.2.先操作数据库还是缓存4.3.3.4.3.如何确保数据库与缓存操作原子性4.3.3.4.3.1.单体系统4.3.3.4.3.2.分布式系统4.3.3.5.最佳实践4.3.3.5.1. 实现商铺和缓存与数据库双写一致4.3.3.5.2. 查询数据时1.先查询缓存2.如果缓存命中直接返回3.如果缓存未命中则查询数据库4.将数据库数据写入缓存5.返回结果4.3.3.5.3. 修改数据库时1.先修改数据库2.然后删除缓存3.确保两者的原子性4.3.4.缓存穿透4.3.4.1.产生原因(定义)4.3.4.2.缓存穿透问题的解决方案4.3.4.2.1.⭐方案一缓存空对象4.3.4.2.2.方案二布隆过滤4.3.4.2.2.其它4.3.5.缓存雪崩4.3.5.1 产生原因(定义)4.3.5.2 缓存穿透的解决方案4.3.6.缓存击穿热点Key4.3.6.1.产生原因4.3.6.1.1.热点Key(1).在某一时段被高并发访问(2).缓存重建耗时较长4.3.6.2.解决方案4.3.6.2.1.方案一互斥锁1. 思路2. 优点3. 缺点4.利用互斥锁解决缓存击穿问题4.3.6.2.2.方案二逻辑过期1.思路2.优点3.缺点4.利用逻辑过期解决缓存击穿问题4.3.6.2.3.方案对比4.3.7.Redis缓存工具封装4.2.商家查询的缓存功能 4.3.1.认识缓存 4.3.1.1.什么是缓存 就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有避震器,防止车体加速后因惯性,在酷似U字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要避震器,防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪; 这在实际开发中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技术; 缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码(例如: 例1:Static final ConcurrentHashMapK,V map new ConcurrentHashMap(); 本地用于高并发例2:static final CacheK,V USER_CACHE CacheBuilder.newBuilder().build(); 用于redis等缓存例3:Static final MapK,V map new HashMap(); 本地缓存由于其被Static修饰,所以随着类的加载而被加载到内存之中,作为本地缓存,由于其又被final修饰,所以其引用(例3:map)和对象(例3:new HashMap())之间的关系是固定的,不能改变,因此不用担心赋值()导致缓存失效; 缓存定义: 一种具备高效读写能力的数据暂存区域。 4.3.1.2.缓存的作用 1.为什么要使用缓存 一句话:因为速度快,好用 缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力。 实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存来作为避震器,系统是几乎撑不住的,所以企业会大量运用到缓存技术; 但是缓存也会增加代码复杂度和运营的成本: 总结来说缓存的作用: 1.降低后端负载2.提高服务读写响应速度 2.如何使用缓存 实际开发中,会构筑多级缓存来使系统运行速度进一步提升,例如:本地缓存与redis中的缓存并发使用。 浏览器缓存主要是存在于浏览器端的缓存。 **应用层缓存**可以分为tomcat本地缓存比如之前提到的map或者是使用redis作为缓存。 **数据库缓存**在数据库中有一片空间是 buffer pool增改查数据都会先加载到mysql的缓存中。 **CPU缓存**当代计算机最大的问题是 cpu性能提升了但内存读写速度没有跟上所以为了适应当下的情况增加了cpu的L1L2L3级的缓存。 3. 添加商户缓存 在我们查询商户信息时我们是直接操作从数据库中去进行查询的大致逻辑是这样直接查询数据库那肯定慢所以我们需要增加缓存。 GetMapping(/{id}) public Result queryShopById(PathVariable(id) Long id) {//这里是直接查询数据库return shopService.queryById(id); }4. 缓存模型和思路 标准的操作方式就是查询数据库之前先查询缓存如果缓存数据存在则直接从缓存中返回如果缓存数据不存在再查询数据库然后将数据存入redis。 代码如下 代码思路如果缓存有则直接返回如果缓存不存在则查询数据库然后存入redis。 修改ShopController.java GetMapping(/{id})public Result queryShopById(PathVariable(id) Long id) {// select * from tb_shop where id ?return shopService.queryById(id);}修改接口IShopService.java public interface IShopService extends IServiceShop {/*** param* return com.hmdp.dto.Result* description //通过id定位商户* param: id* date 2023/2/12 22:43* author wty**/Result queryById(Long id); } 修改ShopMapper.java增加注解 Mapper public interface ShopMapper extends BaseMapperShop {}修改ShopServiceImpl.java Service public class ShopServiceImpl extends ServiceImplShopMapper, Shop implements IShopService {Autowiredprivate ShopMapper shopMapper;Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result queryById(Long id) {// 1.从Redis中查询商铺缓存 cache:shop: idString shopJson stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY id);// 2.判断缓存是否命中if (StrUtil.isNotBlank(shopJson)) {// 2.1如果存在直接返回商铺信息Shop shop JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);} else {// 2.2如果不存在// 2.2.1根据id查询数据库的商铺信息LambdaQueryWrapperShop wrapper new LambdaQueryWrapper();wrapper.eq(Shop::getId, id);Shop shop shopMapper.selectOne(wrapper);// 2.2.2 如果不存在直接返回错误if (null shop) {return Result.fail(您要查询的店铺不存在!);} else {// 2.2.2 如果存在先写入Redis然后返回String jsonShop JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY id, jsonShop);return Result.ok(shop);}}修改RedisConstants.java public static final Long CACHE_NULL_TTL 2L;public static final Long CACHE_SHOP_TTL 30L;public static final String CACHE_SHOP_KEY cache:shop:;重启项目 点击101茶餐厅 查看Redis 再刷新一下界面发现缓存生效 给店铺的类型做一个缓存 修改ShopTypeController.java RestController RequestMapping(/shop-type) public class ShopTypeController {Resourceprivate IShopTypeService typeService;GetMapping(list)public Result queryTypeList() {return typeService.queryTypeList();} }修改IShopTypeService.java public interface IShopTypeService extends IServiceShopType {/*** param* return com.hmdp.dto.Result* description //查询店铺类别* date 2023/2/13 0:11* author wty**/Result queryTypeList(); } 修改ShopTypeServiceImpl.java Service public class ShopTypeServiceImpl extends ServiceImplShopTypeMapper, ShopType implements IShopTypeService {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result queryTypeList() {// 1.从Redis中查询商铺种类Long size stringRedisTemplate.opsForList().size(RedisConstants.CACHE_SHOP_TYPE_KEY);//特殊用法通过stringRedisTemplate.opsForList().range(key, 0, -1)可以查询索引第一个到索引倒数第一个即所有数据ListString lists stringRedisTemplate.opsForList().range(RedisConstants.CACHE_SHOP_TYPE_KEY, 0, size - 1);// 2.查询商铺种类是否存在if (!lists.isEmpty()) {// 2.1商铺种类对象如果存在则返回该对象ArrayListShopType typeList new ArrayList();for (String list : lists) {typeList.add(JSONUtil.toBean(list, ShopType.class));}return Result.ok(typeList);} else {// 2.2商铺种类对象如果不存在则查询数据库ListShopType typeList query().orderByAsc(sort).list();if (typeList.isEmpty()) {return Result.fail(店铺分类不存在!);} else {// 3.如果存在就保存到redis中for (ShopType shopType : typeList) {stringRedisTemplate.opsForList().rightPushAll(RedisConstants.CACHE_SHOP_TYPE_KEY, JSONUtil.toJsonStr(shopType));}// 4.返回店铺种类return Result.ok(typeList);}}} } 重启应用发现启动时间如下 查看redis中数据如下 再刷新界面时间明显减少 4.3.1.3.缓存的成本 1.开发成本2.运维成本3.一致性问题 4.3.2.添加redis缓存 4.3.3.缓存更新策略 缓存更新是redis为了节约内存而设计出来的一个东西主要是因为内存数据宝贵当我们向redis插入太多数据此时就可能会导致缓存中的数据过多所以redis会对部分数据进行更新或者把他叫为淘汰更合适。 4.3.3.1.三种策略 (1).内存淘汰:Redis自带的内存淘汰机制 redis自动进行当redis内存达到咱们设定的max-memery的时候会自动触发淘汰机制淘汰掉一些不重要的数据(可以自己设置策略方式)。 (2).过期淘汰:利用expire命令给数据设置过期时间 当我们给redis设置了过期时间ttl之后redis会将超时的数据进行删除方便咱们继续使用缓存。 (3).主动更新:主动完成数据库与缓存的同时更新 我们可以手动调用方法把缓存删掉通常用于解决缓存和数据库不一致问题。 4.3.3.2.策略选择 4.3.3.2.1.低一致性需求 使用内存淘汰机制例如店铺类型的查询缓存。 4.3.3.3.2.高一致性需求 主动更新为主超时剔除作为兜底方案。例如店铺详情查询的缓存。 4.3.3.3.主动更新的方案 由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在,其后果是: 用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等;怎么解决呢主动更新有如下几种方案 方案一Cache Aside Pattern 人工编码方式缓存调用者在更新完数据库后再去更新缓存也称之为双写方案。 方案二Read/Write Through Pattern : 由系统本身完成数据库与缓存的问题交由系统本身去处理。 方案三Write Behind Caching Pattern 调用者只操作缓存其他线程去异步处理数据库实现最终一致。 4.3.3.3.1.Cache Aside 1.缓存调用者在更新数据库的同时完成对完成的更新 一致性良好实现难度一般 4.3.3.3.2.Read/Write Through 缓存与数据库集成为一个服务服务保证两者的一致性对外暴露API接口。调用者调用API无需知道自己操作的是数据库还是缓存不关心一致性。 一致性优秀实现复杂性能一般 4.3.3.3.3.Write Back 缓存调用者的CRUD都针对缓存完成。由独立线程异步的将缓存数据写到数据库实现最终一致。 一致性差性能好实现复杂 4.3.3.4.Cache Aside的模式选择 综合考虑使用方案一Cache Aside但是方案一调用者如何处理呢这里有几个问题 操作缓存和数据库时有三个问题需要考虑 如果采用第一个方案那么假设我们每次操作数据库后都操作缓存但是中间如果没有人查询那么这个更新动作实际上只有最后一次生效中间的更新动作意义并不大我们可以把缓存删除等待再次查询时将缓存中的数据加载出来 4.3.3.4.1.更新缓存还是删除缓存 更新缓存会产生无效更新并且存在较大的线程安全问题每次更新数据库都更新缓存无效写操作较多。 √删除缓存本质是延迟更新没有无效更新线程安全问题相对较低更新数据库时让缓存失效查询时再更新缓存。 4.3.3.4.2.先操作数据库还是缓存 √(1).先更新数据再删除缓存 在满足原子性的情况下安全问题概率较低 (2).先删除缓存再更新数据库 安全问题概率较高 4.3.3.4.3.如何确保数据库与缓存操作原子性 4.3.3.4.3.1.单体系统 将缓存与数据库操作放在一个事务。 4.3.3.4.3.2.分布式系统 利用分布式事务(TCC)机制。 4.3.3.5.最佳实践 应该具体操作缓存还是操作数据库我们应当是先操作数据库再删除缓存原因在于如果你选择第一种方案在两个线程并发来访问时假设线程1先来他先把缓存删了此时线程2过来他查询缓存数据并不存在此时他写入缓存当他写入缓存后线程1再执行更新动作时实际上写入的就是旧的数据新的数据被旧数据覆盖了。 先操作缓存还是先操作数据库 先删除缓存再操作数据库先操作数据库再删除缓存 总结 4.3.3.5.1. 实现商铺和缓存与数据库双写一致 核心思路如下 修改ShopController中的业务逻辑满足下面的需求 根据id查询店铺时如果缓存未命中则查询数据库将数据库结果写入缓存并设置超时时间。 根据id修改店铺时先修改数据库再删除缓存 修改重点代码1修改ShopServiceImpl的queryById方法 设置redis缓存时添加过期时间 ShopServiceImpl.java // 3.设置缓存的超时时间stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY id, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);修改重点代码2 代码分析通过之前的淘汰我们确定了采用删除策略来解决双写问题当我们修改了数据之后然后把缓存中的数据进行删除查询时发现缓存中没有数据则会从mysql中加载最新的数据从而避免数据库和缓存不一致的问题 修改ShopController.java PutMappingpublic Result updateShop(RequestBody Shop shop) {// 写入数据库return shopService.updateShop(shop);}更新 Result updateShop(Shop shop);更新ShopServiceImpl.java OverrideTransactionalpublic Result updateShop(Shop shop) {// 1.更新mysql数据库updateById(shop);// 2.因为下面是根据id进行删除所以必须要保证id存在if (null ! shop.getId()) {// 3.删除redis缓存stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY shop.getId());}// 4.返回return Result.ok();}启动程序 看一下终端后台 查询一下redis数据库 刷新一下看一下TTL设置了有效期 借助postMan工具进行更新操作(因为更新是管理员操作这里做一下模拟) 更改名字为新疆绝味烤肉 点击send后发现是405 原来是地址错误了我们要用nginx的地址 更换地址 发现也是报错原来是更新的时候时间戳不能还是原来那个 于是在PostMan的json中去掉坐标和时间的属性 再点击send,说明更新成功了 我们看IDEA也有更新的语句 数据库中更改了 再看Redis中没有数据了 再次刷浏览器发现名字被修改了。 4.3.3.5.2. 查询数据时 1.先查询缓存 2.如果缓存命中直接返回 3.如果缓存未命中则查询数据库 4.将数据库数据写入缓存 5.返回结果 4.3.3.5.3. 修改数据库时 1.先修改数据库 2.然后删除缓存 3.确保两者的原子性 4.3.4.缓存穿透 4.3.4.1.产生原因(定义) 客户端请求的数据在缓存中和数据库中都不存在这样缓存永远不会生效这些请求都会打到数据库。 4.3.4.2.缓存穿透问题的解决方案 4.3.4.2.1.⭐方案一缓存空对象 (1).思路 对于不存在的数据也在Redis建立缓存值为空并设置一个较短的TTL时间。 当我们客户端访问不存在的数据时先请求redis但是此时redis中没有数据此时会访问到数据库但是数据库中也没有数据这个数据穿透了缓存直击数据库我们都知道数据库能够承载的并发不如redis这么高如果大量的请求同时过来访问这种不存在的数据这些请求就都会访问到数据库简单的解决方案就是哪怕这个数据在数据库中也不存在我们也把这个数据存入到redis中去这样下次用户过来访问这个不存在的数据那么在redis中也能找到这个数据就不会进入到缓存了。(2).优点 实现简单维护方便。(3).缺点 额外的内存消耗。 短期的数据不一致问题。 4.3.4.2.2.方案二布隆过滤 (1).思路 利用布隆过滤算法在请求进入Redis之前先判断是否存在如果不存在则直接拒绝请求。 布隆过滤器其实采用的是哈希思想来解决这个问题通过一个庞大的二进制数组走哈希思想去判断当前这个要查询的这个数据是否存在如果布隆过滤器判断存在则放行这个请求会去访问redis哪怕此时redis中的数据过期了但是数据库中一定存在这个数据在数据库中查询出来这个数据后再将其放入到redis中 假设布隆过滤器判断这个数据不存在则直接返回 这种方式优点在于节约内存空间存在误判误判原因在于布隆过滤器走的是哈希思想只要哈希思想就可能存在哈希冲突。 (2).优点 内存占用少。(3).缺点 实现复杂 存在误判的可能性 4.3.4.2.2.其它 (1).做好数据的基础格式校验(2).加强用户权限校验(3).做好热点参数的限流 核心思路如下 在原来的逻辑中我们如果发现这个数据在mysql中不存在直接就返回404了这样是会存在缓存穿透问题的 现在的逻辑中如果这个数据不存在我们不会返回404 还是会把这个数据写入到Redis中并且将value设置为空欧当再次发起查询时我们如果发现命中之后判断这个value是否是null如果是null则是之前写入的数据证明是缓存穿透数据如果不是则直接返回数据。 这里讲一下查看源码帮助文档的方式 打开Terminal,与当前项目中pom.xml的统计目录输入以下指令 mvn dependency:sources mvn dependency:resolve -Dclassifierjavadoc如果只想单独下载某个依赖, 可以输入 mvn dependency:sources -DincludeArtifactIdsspring-rabbit这时候鼠标悬浮到想看的代码就会有注释 更改ShopServiceImpl.java 代码如下ShopServiceImpl.java Overridepublic Result queryById(Long id) {// 1.从Redis中查询商铺缓存 cache:shop: idString shopJson stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY id);// 2.判断缓存是否命中if (StrUtil.isNotBlank(shopJson)) {// 2.1如果存在直接返回商铺信息Shop shop JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);} else {//现在shopJson有2种可能空字符串和nullif (.equals(shopJson)) {// 如果是空字符串返回一个错误信息return Result.fail(您要查询的店铺不存在!);}// 2.2如果为null就进行下面的操作// 2.2.1根据id查询数据库的商铺信息LambdaQueryWrapperShop wrapper new LambdaQueryWrapper();wrapper.eq(Shop::getId, id);Shop shop shopMapper.selectOne(wrapper);// 2.2.2 如果不存在直接返回错误if (null shop) {// 将空值写入redis并且设置有效期(缓存穿透)stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY id, , RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return Result.fail(您要查询的店铺不存在!);} else {// 2.2.2 如果存在先写入Redis然后返回String jsonShop JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY id, jsonShop);// 3.设置缓存的超时时间stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY id, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 返回return Result.ok(shop);}}}修改RedisConstants.java public static final Long CACHE_NULL_TTL 2L;重启应用 打开连接如下 发现IDEA后台输出查询语句 清空后刷新页面输入一个没有的id 看IDEA后台有sql输出 清空IDEA后台后再刷新当前失败的跳转页发现没有sql输出了说明是用的Redis缓存我们看一下Redis的图形界面 看以下TTL 同理我们给店铺种类也可以加上相同的缓存null的逻辑防止缓存击穿 修改ShopTypeServiceImpl.java ShopTypeServiceImpl.java Service public class ShopTypeServiceImpl extends ServiceImplShopTypeMapper, ShopType implements IShopTypeService {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result queryTypeList() {// 1.从Redis中查询商铺种类Long size stringRedisTemplate.opsForList().size(RedisConstants.CACHE_SHOP_TYPE_KEY);//特殊用法通过stringRedisTemplate.opsForList().range(key, 0, -1)可以查询索引第一个到索引倒数第一个即所有数据ListString lists stringRedisTemplate.opsForList().range(RedisConstants.CACHE_SHOP_TYPE_KEY, 0, size - 1);// 2.查询商铺种类是否存在if (!lists.isEmpty()) {// 2.1商铺种类对象如果存在则返回该对象ArrayListShopType typeList new ArrayList();for (String list : lists) {typeList.add(JSONUtil.toBean(list, ShopType.class));}return Result.ok(typeList);} else {// 现在lists有2种可能空字符串和nullif (.equals(lists)) {// 如果是空字符串返回一个错误信息return Result.fail(您要查询的店铺种类不存在!);}// 2.2商铺种类对象如果不存在则查询数据库ListShopType typeList query().orderByAsc(sort).list();if (typeList.isEmpty()) {// 设置空值防止缓存击穿stringRedisTemplate.opsForList().rightPush(RedisConstants.CACHE_SHOP_TYPE_KEY, );return Result.fail(店铺分类不存在!);} else {// 3.如果存在就保存到redis中for (ShopType shopType : typeList) {stringRedisTemplate.opsForList().rightPush(RedisConstants.CACHE_SHOP_TYPE_KEY, JSONUtil.toJsonStr(shopType));// 设置缓存时间stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_TYPE_KEY, RedisConstants.CACHE_SHOP_TYPE__TTL, TimeUnit.MINUTES);}// 4.返回店铺种类return Result.ok(typeList);}}}总结 缓存穿透产生的原因是什么 用户请求的数据在缓存中和数据库中都不存在不断发起这样的请求给数据库带来巨大压力 缓存穿透的解决方案有哪些 被动措施 缓存null值布隆过滤 主动措施 增强id的复杂度避免被猜测id规律做好数据的基础格式校验加强用户权限校验做好热点参数的限流 4.3.5.缓存雪崩 4.3.5.1 产生原因(定义) 在同一时段大量的缓存key同时失效或者Redis服务宕机导致大量请求到达数据库带来巨大压力。 4.3.5.2 缓存穿透的解决方案 (1).给不同的Key的TTL添加随机值(2).利用Redis集群提高服务的可用性(3).给缓存业务添加降级限流策略(4).给业务添加多级缓存 4.3.6.缓存击穿热点Key 4.3.6.1.产生原因 缓存击穿问题也叫热点Key问题就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了无数的请求访问会在瞬间给数据库带来巨大的冲击。 4.3.6.1.1.热点Key 逻辑分析假设线程1在查询缓存之后本来应该去查询数据库然后把这个数据重新加载到缓存的此时只要线程1走完这个逻辑其他线程就都能从缓存中加载这些数据了但是假设在线程1没有走完的时候后续的线程2线程3线程4同时过来访问当前这个方法 那么这些线程都不能从缓存中查询到数据那么他们就会同一时刻来访问查询缓存都没查到接着同一时间去访问数据库同时的去执行数据库代码对数据库访问压力过大。 (1).在某一时段被高并发访问 (2).缓存重建耗时较长 常见的缓存击穿问题解决方案有两种。 4.3.6.2.解决方案 4.3.6.2.1.方案一互斥锁 1. 思路 给缓存重建过程加锁确保重建过程只有一个线程执行其它线程等待。 因为锁能实现互斥性。假设线程过来只能一个人一个人的来访问数据库从而避免对于数据库访问压力过大但这也会影响查询的性能因为此时会让查询的性能从并行变成了串行我们可以采用tryLock方法 double check来解决这样的问题。 假设现在线程1过来访问他查询缓存没有命中但是此时他获得到了锁的资源那么线程1就会一个人去执行逻辑假设现在线程2过来线程2在执行过程中并没有获得到锁那么线程2就可以进行到休眠直到线程1把锁释放后线程2获得到锁然后再来执行逻辑此时就能够从缓存中拿到数据了。 2. 优点 实现简单 没有额外内存消耗 一致性好 3. 缺点 等待导致性能下降 有死锁风险 4.利用互斥锁解决缓存击穿问题 核心思路相较于原来从缓存中查询不到数据后直接查询数据库而言现在的方案是 进行查询之后如果从缓存没有查询到数据则进行互斥锁的获取获取互斥锁后判断是否获得到了锁如果没有获得到则休眠过一会再进行尝试直到获取到锁为止才能进行查询 如果获取到了锁的线程再去进行查询查询后将数据写入redis再释放锁返回数据利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑防止缓存击穿。 操作锁的代码 核心思路就是利用redis的setnx方法来表示获取锁该方法含义是redis中如果没有这个key则插入成功返回1如果当前key被setnx赋值其它指令再想赋值的时候会返回0可以当作一个互斥锁。 那这个互斥锁如何释放呢我们模拟以下只需要删除该属性即可。 特别注意如果程序错误没有删除掉该锁那么后续业务会一直处于等待的状态这样是不好的那么我们的解决方案就是给锁加上有效期。 在stringRedisTemplate中返回true 如果有这个key则插入失败则返回0在stringRedisTemplate返回false我们可以通过true或者是false来表示是否有线程成功插入key成功插入的key的线程我们认为他就是获得到锁的线程。 修改ShopServiceImpl.java 先增加获取锁的逻辑 //尝试获取锁private boolean tryLock(String key) {// setnx setIfAbsent// 这里注意获取锁等待时间是10秒Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);// 这里注意flag是包装类Boolean而方法的返回值是基本类型boolean// 这里要拆箱后再返回结果return BooleanUtil.isTrue(flag);}//释放锁private void unLock(String key) {stringRedisTemplate.delete(key);}修改RedisConstants.java添加 public static final Long LOCK_SHOP_TTL 10L;修改ShopServiceImpl.java Overridepublic Result queryById(Long id) {// 解决缓存穿透//Shop shop queryWithPassThough(id);// 互斥锁解决缓存击穿Shop shop queryWithMutex(id);if (null shop) {Result.fail(您要查询的店铺不存在!);}return Result.ok(shop);}缓存击穿核心处理逻辑ShopServiceImpl.java public Shop queryWithMutex(Long id) {boolean flag;Shop shop null;try {do {// 1.从Redis中查询商铺缓存 cache:shop: idString shopJson stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY id);// 2.判断缓存是否命中if (StrUtil.isNotBlank(shopJson)) {// 2.1如果存在直接返回商铺信息shop JSONUtil.toBean(shopJson, Shop.class);return shop;}//现在shopJson有2种可能空字符串和nullif (null ! shopJson) {// 如果是空字符串返回一个错误信息//return Result.fail(您要查询的店铺不存在!);return null;}// 1.1解决缓存击穿尝试获取锁flag tryLock(RedisConstants.LOCK_SHOP_KEY);// 1.2判断互斥锁是否获取成功if (!flag) {// 1.2.1获取失败 就休眠一会儿然后查询Redis 的shopThread.sleep(50);}} while (flag false);// 1.2.2获取成功 就直接进行下面的步骤// 2.2如果为null就进行下面的操作// 2.2.1根据id查询数据库的商铺信息LambdaQueryWrapperShop wrapper new LambdaQueryWrapper();wrapper.eq(Shop::getId, id);shop shopMapper.selectOne(wrapper);// TODO 模拟Redis击穿后重建Redis的时间,可以去掉Thread.sleep(200);// 2.2.2 如果不存在直接返回错误if (null shop) {// 将空值写入redis并且设置有效期(缓存穿透)stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY id, , RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);//return Result.fail(您要查询的店铺不存在!);return null;}// 2.2.2 如果存在先写入Redis然后返回String jsonShop JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY id, jsonShop);// 3.设置缓存的超时时间stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY id, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 4.释放锁unLock(RedisConstants.LOCK_SHOP_KEY);}// 5.返回return shop;}注意do{}while(条件)是说条件不成立跳出循环条件成立继续循环 重启程序 打开压力测试工具JMeter 定义1000个线程 循环次数5秒 Http配置如下 点击发起请求 查看结果树 查看汇总报告 4.3.6.2.2.方案二逻辑过期 1.思路 热点key缓存永不过期而是设置一个逻辑过期时间查询到数据时通过对逻辑过期时间判断来决定是否需要重建缓存。重建缓存也通过互斥锁保证单线程执行。重建缓存利用独立线程异步执行。其它线程无需等待直接查询到的旧数据即可。 方案分析我们之所以会出现这个缓存击穿问题主要原因是在于我们对key设置了过期时间假设我们不设置过期时间其实就不会有缓存击穿的问题但是不设置过期时间这样数据不就一直占用我们内存了吗我们可以采用逻辑过期方案。 我们把过期时间设置在 redis的value中注意这个过期时间并不会直接作用于redis而是我们后续通过逻辑去处理。假设线程1去查询缓存然后从value中判断出来当前的数据已经过期了此时线程1去获得互斥锁那么其他线程会进行阻塞获得了锁的线程他会开启一个 线程去进行 以前的重构数据的逻辑直到新开的线程完成这个逻辑后才释放锁 而线程1直接进行返回假设现在线程3过来访问由于线程线程2持有着锁所以线程3无法获得锁线程3也直接返回数据只有等到新开的线程2把重建数据构建完后其他线程才能走返回正确的数据。 这种方案巧妙在于异步的构建缓存缺点在于在构建完缓存之前返回的都是脏数据。 2.优点 线程无需等待性能较好。 3.缺点 不保证一致性。 有额外内存消耗。 实现复杂。 4.利用逻辑过期解决缓存击穿问题 需求修改根据id查询商铺的业务基于逻辑过期方式来解决缓存击穿问题 思路分析当用户开始查询redis时判断是否命中如果没有命中则直接返回空数据不查询数据库而一旦命中后将value取出判断value中的过期时间是否满足如果没有过期则直接返回redis中的数据如果过期则在开启独立线程后直接返回之前的数据独立线程去重构数据重构完成后释放互斥锁。 如果封装数据因为现在redis中存储的数据的value需要带上过期时间此时要么你去修改原来的实体类要么你新建一个实体类我们采用第二个方案这个方案对原来代码没有侵入性。 步骤一、 新增类RedisData .java添加逻辑过期时间 RedisData.java代码如下 Data AllArgsConstructor NoArgsConstructor public class RedisData {private LocalDateTime expireTime;/*** data可以理解存储的是Shop对象*/private Object data;}步骤二、 在ShopServiceImpl 新增此方法利用单元测试进行缓存预热 修改ShopServiceImpl.java 增加一个方法用来保存和设置有效期 public void saveShop2Redis(Long id, Long seconds) {// 1.查询shop数据LambdaQueryWrapperShop wrapper new LambdaQueryWrapper();wrapper.eq(Shop::getId, id);// select * from tb_shop where id ?Shop shop shopMapper.selectOne(wrapper);// 2.封装逻辑过期时间RedisData redisData new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(seconds));// 3.写入RedisstringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY id, JSONUtil.toJsonStr(redisData));}在单元测试类中测试 代码如下 CommentApplicationTests.java SpringBootTest class CommentApplicationTests {Resourceprivate ShopServiceImpl shopService;Testpublic void testSave() {shopService.saveShop2Redis(1L, 10L);}}点击测试 查看后台 查看Redis图形界面有效期存进去了 步骤三正式代码 修改ShopServiceImpl逻辑过期核心代码 public Shop queryWithLogicalExpire(Long id) {// 1.从Redis中查询商铺缓存 cache:shop: idString redisData_Str stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY id);// 2.判断缓存是否命中if (StrUtil.isBlank(redisData_Str)) {// 2.1如果未命中Redis缓存信息,则直接返回空return null;}// 2.2如果命中了Redis缓存信息,判断缓存是否过期RedisData redisData JSONUtil.toBean(redisData_Str, RedisData.class);Object o redisData.getData();JSONObject data (JSONObject) o;Shop shop JSONUtil.toBean(data, Shop.class);LocalDateTime expireTime redisData.getExpireTime();// 3.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {// 3.1未过期直接返回店铺信息return shop;}// 3.2已过期需要缓存重建// 4 获取互斥锁boolean flag tryLock(RedisConstants.LOCK_SHOP_KEY id);// 5. 判断是否获取锁成功if (flag) {// 5.1 获取到锁 首先再判断是否过期过期// 然后就开启线程 根据id查询数据库 将商铺数据写入Redis并设置逻辑过期时间,释放互斥锁ExecutorService executorService newFixedThreadPool(10);/*executorService.submit(new Runnable() {Overridepublic void run() {try {// 重建缓存// TODO saveShop2Redis(id, RedisConstants.CACHE_SHOP_TYPE__TTL);saveShop2Redis(id, 20L);} catch (Exception e) {e.printStackTrace();} finally {// 释放锁unLock(RedisConstants.LOCK_SHOP_KEY id);}}});*/// 函数式接口executorService.submit(() - {try {//再次检查缓存有没有过期防止在高并发环境下缓存多次重建LocalDateTime time JSONUtil.toBean(stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY id),RedisData.class).getExpireTime();// 3.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {// 3.1未过期直接返回店铺信息// run方法是无参的所以这里没有返回值return;}// 重建缓存// TODO saveShop2Redis(id, RedisConstants.CACHE_SHOP_TYPE__TTL);saveShop2Redis(id, 20L);} catch (Exception e) {e.printStackTrace();} finally {// 释放锁unLock(RedisConstants.LOCK_SHOP_KEY id);}});}// 5.2未获取到锁还是获取到锁都要 返回商铺信息同3.1return shop;}修改ShopServiceImpl.java添加线程池 public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());}修改RedisConstants.java 添加锁的key和有效期10秒 public static final String LOCK_SHOP_KEY lock:shop:;public static final Long LOCK_SHOP_TTL 10L;为了测试一致性 先更改数据库信息 UPDATE tb_shop SET NAME 102茶餐厅 WHERE id 1;此时Redis中数据为 重启应用 继续使用JMeter 配置如下 运行后发现查询结果是旧的。 睡眠了50ms后更新成最新的了 查看Redis已经是最新的了 后台只有一次sql的查询 验证了不保证一致性 4.3.6.2.3.方案对比 **互斥锁方案**由于保证了互斥性所以数据一致且实现简单因为仅仅只需要加一把锁而已也没其他的事情需要操心所以没有额外的内存消耗缺点在于有锁就有死锁问题的发生且只能串行执行性能肯定受到影响。 逻辑过期方案 线程读取过程中不需要等待性能好有一个额外的线程持有锁去进行重构数据但是在重构数据完成前其他的线程只能返回之前的数据且实现起来麻烦。 4.3.7.Redis缓存工具封装 基于StringRedisTemplate封装一个缓存工具类满足下列需求 方法1将任意Java对象序列化为json并存储在string类型的key中并且可以设置TTL过期时间。方法2将任意Java对象序列化为json并存储在string类型的key中并且可以设置逻辑过期时间用于处理缓。 存击穿问题 方法3根据指定的key查询缓存并反序列化为指定类型利用缓存空值的方式解决缓存穿透问题。方法4根据指定的key查询缓存并反序列化为指定类型需要利用逻辑过期解决缓存击穿问题。 将逻辑进行封装 新建工具类CacheClient.java CacheClient.java代码如下 Component Slf4j public class CacheClient {Resourceprivate StringRedisTemplate stringRedisTemplate;/*** param* return void* description //将任意Java对象序列化为json并存储在string类型的key中并且可以设置TTL过期时间* param: key* param: value* param: time* param: unit* date 2023/2/14 9:17* author wty**/public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}/*** param* return void* description //将任意Java对象序列化为json并存储在string类型的key中并且可以设置逻辑过期时间用于处理缓存击穿问题* param: key* param: value* param: time* param: unit* date 2023/2/14 9:20* author wty**/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)));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData), time, unit);}/*** param* return T 泛型(实体类的类型)* description // 根据指定的key查询缓存并反序列化为指定类型利用缓存空值的方式解决缓存穿透问题* param: keyPrefix 设置一个常量* param: id 泛型(可以为任意类型)* param: type T的类型* param: function* param: time 有效期* param: unit 有效期的时间单位分钟/小时/秒* date 2023/2/14 9:48* author wty**/public T, ID T queryWithPassThough(String keyPrefix, ID id, ClassT type, FunctionID, T function, Long time, TimeUnit unit) {String key keyPrefix id;// 1.从Redis中查询实体对象的缓存例如商铺就是 cache:shop: idString json stringRedisTemplate.opsForValue().get(key);// 2.判断缓存是否命中if (StrUtil.isNotBlank(json)) {// 2.1如果存在直接返回对象信息T t JSONUtil.toBean(json, type);return t;} else {//现在json有2种可能空字符串和nullif (.equals(json)) {// 如果是空字符串返回一个空外层调用的时候抛出错误信息//return Result.fail(您要查询的店铺不存在!);return null;}// 2.2如果为null就进行下面的操作// 2.2.1根据id查询数据库的商铺信息,这里用函数式接口实现/* LambdaQueryWrapperT wrapper new LambdaQueryWrapper();// select * from tb_shop where id ?wrapper.eq(Shop::getId, id);Shop shop shopMapper.selectOne(wrapper);*/T t function.apply(id);// 2.2.2 如果不存在直接返回错误if (null t) {// 将空值写入redis并且设置有效期(缓存穿透) 2分钟stringRedisTemplate.opsForValue().set(key, , RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);// 上层返回以下信息//return Result.fail(您要查询的店铺不存在!);return null;} else {// 2.2.2 如果存在先写入Redis然后返回,并且设置缓存的超时时间,使用上面的方法即可set(key, t, time, unit);// 返回return t;}}} } 在ShopServiceImpl 中更改 Resource private CacheClient cacheClient;Overridepublic Result queryById(Long id) {// 解决缓存穿透//Shop shop queryWithPassThough(id);// 解决缓存穿透(使用模板)//cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, id_key - getById(id_key), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 解决缓存穿透(使用模板) 方法引用Shop shop cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 互斥锁解决缓存击穿//Shop shop queryWithMutex(id);// 逻辑过期解决缓存击穿//Shop shop queryWithLogicalExpire(id);if (null shop) {Result.fail(您要查询的店铺不存在!);}return Result.ok(shop);}重启应用还用shop0作为测试用例 多次刷新发现只有第一次用了sql查询数据库剩下都是redis 发现空值已经存入redis 下面封装逻辑过期解决缓击击穿 修改CacheClient.java 添加代码 Component Slf4j public class CacheClient {Resourceprivate StringRedisTemplate stringRedisTemplate;/*** param* return void* description //将任意Java对象序列化为json并存储在string类型的key中并且可以设置逻辑过期时间用于处理缓存击穿问题* param: key* param: value* param: time* param: unit* date 2023/2/14 9:20* author wty**/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)));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData), time, unit);}/*** param* return T* description //逻辑过期解决缓存击穿* param: keyPrefix 常量 例如shop是 CACHE_SHOP_KEY cache:shop:* param: id 泛型 可以是任意类型* param: type 泛型* param: function 函数式接口ID是参数 T是返回值类型* param: time 有效期* param: unit 有效期类型* date 2023/2/14 12:14* author wty**/public T, ID T queryWithLogicalExpire(String keyPrefix, ID id, ClassT type, FunctionID, T function, Long time, TimeUnit unit) {String key keyPrefix id;// 1.从Redis中查询实体类缓存 例如shop是 cache:shop: idString redisData_Str stringRedisTemplate.opsForValue().get(key);// 2.判断缓存是否命中if (StrUtil.isBlank(redisData_Str)) {// 2.1如果未命中Redis缓存信息,则直接返回空return null;}// 2.2如果命中了Redis缓存信息,判断缓存是否过期RedisData redisData JSONUtil.toBean(redisData_Str, RedisData.class);Object o redisData.getData();JSONObject data (JSONObject) o;T t JSONUtil.toBean(data, type);LocalDateTime expireTime redisData.getExpireTime();// 3.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {// 3.1未过期直接返回实体类信息return t;}// 3.2已过期需要缓存重建// 4 获取互斥锁boolean flag tryLock(RedisConstants.LOCK_SHOP_KEY id, RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);// 5. 判断是否获取锁成功if (flag) {// 5.1 获取到锁 首先再判断是否过期过期// 然后就开启线程 根据id查询数据库 将实体类数据写入Redis并设置逻辑过期时间,释放互斥锁ExecutorService executorService newFixedThreadPool(10);/*executorService.submit(new Runnable() {Overridepublic void run() {try {// 重建缓存// TODO saveShop2Redis(id, RedisConstants.CACHE_SHOP_TYPE__TTL);saveShop2Redis(id, 20L);} catch (Exception e) {e.printStackTrace();} finally {// 释放锁unLock(RedisConstants.LOCK_SHOP_KEY id);}}});*/// 函数式接口executorService.submit(() - {try {//再次检查缓存有没有过期防止在高并发环境下缓存多次重建LocalDateTime expireTime_Thread JSONUtil.toBean(stringRedisTemplate.opsForValue().get(key),RedisData.class).getExpireTime();// 3.判断是否过期if (expireTime_Thread.isAfter(LocalDateTime.now())) {// 3.1未过期直接返回实体类信息// run方法是无参的所以这里没有返回值return;}// 4.重建缓存// 4.1查询数据库T t_thread function.apply(id);// 4.2写入RedissetWithLogicExpire(key, t_thread, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {// 释放锁unLock(RedisConstants.LOCK_SHOP_KEY id);}});}// 5.2未获取到锁还是获取到锁都要 返回商铺信息同3.1return t;}/*** param* return java.util.concurrent.ExecutorService* description //创建固定线程数的线程池* param: nThreads* date 2023/2/14 1:19* author wty**/public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());}/*** param* return boolean* description //尝试获取锁* param: key* param: TTL 有效期例如shop是RedisConstants.LOCK_SHOP_TTL* param: unit 有效期的单位比如小时/分钟/秒* date 2023/2/13 16:37* author wty**/private boolean tryLock(String key, Long TTL, TimeUnit unit) {// setnx setIfAbsent// 这里注意获取锁等待时间是10秒Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, TTL, unit);// 这里注意flag是包装类Boolean而方法的返回值是基本类型boolean// 这里要拆箱后再返回结果return BooleanUtil.isTrue(flag);}/*** param* return void* description //释放锁* param: key* date 2023/2/13 16:45* author wty**/private void unLock(String key) {stringRedisTemplate.delete(key);}} 修改ShopServiceImpl.java Overridepublic Result queryById(Long id) {// 解决缓存穿透//Shop shop queryWithPassThough(id);// 解决缓存穿透(使用模板)//cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, id_key - getById(id_key), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 解决缓存穿透(使用模板) 方法引用//Shop shop cacheClient//.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 互斥锁解决缓存击穿//Shop shop queryWithMutex(id);// 逻辑过期解决缓存击穿Shop shop cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.SECONDS);if (null shop) {return Result.fail(您要查询的店铺不存在!);}return Result.ok(shop);}修改CommentApplicationTests.java SpringBootTest class CommentApplicationTests {Resourceprivate CacheClient cacheClient;Resourceprivate ShopServiceImpl shopService;Testpublic void testLogicExpire() {Shop shop shopService.getById(1L);cacheClient.setWithLogicExpire(RedisConstants.CACHE_SHOP_KEY, shop, 10L, TimeUnit.SECONDS);} } 先跑测试类CommentApplicationTests让Reds中有数据 点击运行测试类后看到Redis中有数据 更新数据库 UPDATE tb_shop SET NAME 104茶餐厅 WHERE id 1;然后运行JMeter请求正常 之后我们修改一下利用互斥锁解决缓存击穿问题的代码变成工具类 修改CacheClient.java public T, ID T queryWithMutex(String keyPrefix, ID id, ClassT type, FunctionID, T function, Long time, TimeUnit unit) {String key keyPrefix id;boolean flag;T t null;try {do {// 1.从Redis中查询实体类的缓存 例如cache:shop: idString json stringRedisTemplate.opsForValue().get(key);// 2.判断缓存是否命中if (StrUtil.isNotBlank(json)) {// 2.1如果存在直接返回实体类信息t JSONUtil.toBean(json, type);return t;}//现在json有2种可能空字符串和nullif (null ! json) {// 如果是空字符串返回一个错误信息//return Result.fail(您要查询的店铺不存在!);return null;}// 1.1解决缓存击穿尝试获取锁flag tryLock(RedisConstants.LOCK_SHOP_KEY id, RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);// 1.2判断互斥锁是否获取成功if (!flag) {// 1.2.1获取失败 就休眠一会儿然后查询Redis 的实体类Thread.sleep(50);}} while (flag false);// 1.2.2获取成功 就直接进行下面的步骤// 2.2如果为null就进行下面的操作// 2.2.1根据id查询数据库的实体类信息/*LambdaQueryWrapperShop wrapper new LambdaQueryWrapper();// select * from tb_shop where id ?wrapper.eq(Shop::getId, id);shop shopMapper.selectOne(wrapper);*/t function.apply(id);// TODO 模拟Redis击穿后重建Redis的时间,可以去掉//Thread.sleep(200);// 2.2.2 如果不存在直接返回错误if (null t) {// 将空值写入redis并且设置有效期(缓存穿透)stringRedisTemplate.opsForValue().set(key, , RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);//return Result.fail(您要查询的店铺不存在!);return null;}// 2.2.2 如果存在先写入Redis然后返回String jsont JSONUtil.toJsonStr(t);stringRedisTemplate.opsForValue().set(key, jsont);// 3.设置缓存的超时时间stringRedisTemplate.expire(key, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 4.释放锁unLock(RedisConstants.LOCK_SHOP_KEY id);}// 5.返回return t;}修改ShopServiceImpl.java Overridepublic Result queryById(Long id) {// 解决缓存穿透//Shop shop queryWithPassThough(id);// 解决缓存穿透(使用模板)//cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, id_key - getById(id_key), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 解决缓存穿透(使用模板) 方法引用/*Shop shop cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);*/// 互斥锁解决缓存击穿//Shop shop queryWithMutex(id);// 互斥锁解决缓存击穿Shop shop cacheClient.queryWithMutex(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 逻辑过期解决缓存击穿(使用模板)/*Shop shop cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);*/if (null shop) {return Result.fail(您要查询的店铺不存在!);}return Result.ok(shop);}先用测试类给redis缓存中添加一条店铺信息然后重复登录该id对应的网址发现redis只会有一次sql的查询即可。 最后为了保证店铺信息的正常加载切换到 ShopServiceImpl.java Overridepublic Result queryById(Long id) {// 解决缓存穿透//Shop shop queryWithPassThough(id);// 解决缓存穿透(使用模板)//cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, id_key - getById(id_key), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 解决缓存穿透(使用模板) 方法引用Shop shop cacheClient.queryWithPassThough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 互斥锁解决缓存击穿//Shop shop queryWithMutex(id);// 互斥锁解决缓存击穿//Shop shop cacheClient.queryWithMutex(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 逻辑过期解决缓存击穿(使用模板)/*Shop shop cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);*/if (null shop) {return Result.fail(您要查询的店铺不存在!);}return Result.ok(shop);}
http://www.hkea.cn/news/14517846/

相关文章:

  • 做网站开发的过程asp.net ftp发布网站
  • 管理课程培训视频教程全集网站seo优化有哪些
  • 搭建网站的必须条件tp框架做展示网站
  • 用别人公司域名做网站网站开发方法是什么
  • 连云港做网站建设html教学网站
  • 崇明建设镇乡镇府网站淘宝电子网站建设论文
  • 做网站编程时容易遇到的问题电商网络推广方案
  • html制作一个个人主页网站公司网站建设一条龙
  • 利用网上菜谱做网站专业做网站推广的公司
  • 用asp做的网站全国网络公司大全
  • 做seo_教你如何选择网站关键词北京市建设工程审核网站
  • 公司建立网站怎么做分录简单的报价表模板
  • .net商城网站开发东莞网站建设技术
  • ui图标素材网站建网站有哪些费用
  • 安徽富通建设有限公司网站天津企业免费建站
  • 网站建设方案大全网站三元素怎么做
  • 铜陵app网站做营销招聘投资网站模板
  • 新加坡的网站域名柳州网站推广最好的公司
  • 做网站一般图片的比例北京中文seo
  • 做办公用品的招聘网站网站备案负责人 更换
  • 网站建设相关知识博客wordpress工具包
  • 备案网站首页地址网站简历文字如何空行
  • 网站建设五年发展规划常用十大浏览器
  • 百度云用流量做网站城市网站联盟
  • 优化大师apk长沙网站优化排名推广
  • 个人网站可以备案网络工程主要是干嘛的
  • 网站建设费税率多少钱个人建网站大概多少钱
  • 注册了网站之后怎么设计公司品牌网络推广方案
  • 曰本做爰网站网站站长登录方式
  • 建站基础公司网站的详细步骤