瑞_Redis_商户查询缓存_添加Redis缓存缓存更新策略

news2024/11/20 15:27:46

文章目录

    • 项目介绍
    • 1 短信登录
    • 2 商户查询缓存
      • 2.1 什么是缓存
        • 2.1.1 缓存的应用场景
        • 2.1.2 为什么要使用缓存
        • 2.1.3 Web应用中缓存的作用
        • 2.1.4 Web应用中缓存的成本
      • 2.2 添加Redis缓存
        • 2.2.1 背景
        • 2.2.2 缓存模型和思路
        • 2.2.3 代码实现
        • 2.2.4 测试
        • 附:IDEA控制台输出自动换行设置
      • 2.3 缓存更新策略
        • 2.3.1 常见的缓存更新三大策略
          • 2.3.1.1 内存淘汰
          • 2.3.1.2 超时剔除
          • 2.3.1.3 主动更新
        • 2.3.2 主动更新策略——数据库缓存不一致的解决方案
        • 2.3.3 双写方案的三个注意事项
          • 2.3.3.1 删除缓存
          • 2.3.3.2 保证缓存与数据库的操作的同时成功或失败
          • 2.3.3.3 先操作数据库再删除缓存
        • 2.3.4 缓存更新策略的最佳实践方案
        • 2.3.5 案例:添加超时剔除和主动更新策略
          • 2.3.5.1 需求
          • 2.3.5.2 代码实现
          • 2.3.5.3 测试
        • 2.3.6 代码资源
      • 2.4 缓存穿透
      • 2.5 缓存雪崩
      • 2.6 缓存击穿
      • 2.7 缓存工具封装

🙊 前言:本文章为瑞_系列专栏之《Redis》的实战篇的商户查询缓存章节的添加Redis缓存以及缓存更新策略小节。由于博主是从B站黑马程序员的《Redis》学习其相关知识,所以本系列专栏主要是针对该课程进行笔记总结和拓展,文中的部分原理及图解等也是来源于黑马提供的资料,特此注明。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!

  • 主机操作系统:Windows10
  • VMware版本: VMware Workstation 16.2.4
  • Linux版本:CentOS 7 64位
  • 远程连接工具:MobaXterm_Personal_23.2
  • Redis版本:redis-6.2.6.tar.gz
  • Redis客户端:resp-2022.2.0.0
  • MySQL版本:8.0.29(5.7+均可)
  • Navicat Premium:15.0.28
  • JDK:1.8

相关链接:《瑞_Java所有相关环境及软件的安装和卸载_图文超详细(持续更新)》
相关链接:《瑞_Redis_短信登录》

瑞&3l

项目介绍

  本文基于B站黑马程序员的《黑马点评》项目进行学习笔记总结和拓展,项目的相关资源和课程视频可以到B站获取。
  博主提供的该项目的相关资源的某度网盘链接:https://pan.baidu.com/s/1N-yr86yTRi3LbQdAL7prEQ?pwd=q0ry

  本项目具有以下功能点,本文为《商户查询缓存》篇的添加Redis缓存以及缓存更新策略小节

在这里插入图片描述

  • 短信登录
    这一块我们会使用redis共享session来实现

  • 商户查询缓存
    通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码中看到对应的内容

  • 优惠卷秒杀
    通过本章节,我们可以学会Redis的计数器功能, 结合Lua完成高性能的redis操作,同时学会Redis分布式锁的原理,包括Redis的三种消息队列

  • 附近的商户
    我们利用Redis的GEOHash来完成对于地理坐标的操作

  • UV统计
    主要是使用Redis来完成统计功能

  • 用户签到
    使用Redis的BitMap数据统计功能

  • 好友关注
    基于Set集合的关注、取消关注,共同关注等等功能,这一块知识咱们之前就讲过,这次我们在项目中来使用一下

  • 达人探店
    基于List来完成点赞列表的操作,同时基于SortedSet来完成点赞的排行榜功能

由于该项目主要是为了学习Redis,所以不会设计为微服务架构,简化代码复杂度,所以采用前后端分离的单体架构

说明

  手机或者app端发起请求,请求我们的nginx服务器,nginx基于七层模型走的事HTTP协议,可以实现基于Lua直接绕开tomcat访问redis,也可以作为静态资源服务器,轻松扛下上万并发, 负载均衡到下游tomcat服务器,打散流量,我们都知道一台4核8G的tomcat,在优化和处理简单业务的加持下,大不了就处理1000左右的并发, 经过nginx的负载均衡分流后,利用集群支撑起整个项目,同时nginx在部署了前端项目后,更是可以做到动静分离,进一步降低tomcat服务的压力,这些功能都得靠nginx起作用,所以nginx是整个项目中重要的一环。

  在 tomcat 支撑起并发流量后,我们如果让 tomcat 直接去访问 Mysql ,根据经验 Mysql 企业级服务器只要上点并发,一般是16或32 核心cpu,32 或64G内存,像企业级mysql加上固态硬盘能够支撑的并发,大概就是4000起~7000左右,上万并发, 瞬间就会让Mysql服务器的cpu,硬盘全部打满,容易崩溃,所以我们在高并发场景下,会选择使用mysql集群,同时为了进一步降低Mysql的压力,同时增加访问的性能,我们也会加入Redis,同时使用Redis集群使得Redis对外提供更好的服务。

在这里插入图片描述

1 短信登录

瑞:见《瑞_Redis_短信登录》




2 商户查询缓存

  本章节基于hm-dianping【1.3Redis代替session的业务流程】的代码,需要请自取

链接:https://pan.baidu.com/s/1DomlH_sXyAkrciXk8-bWww?pwd=z6lu 
提取码:z6lu

2.1 什么是缓存

缓存就是数据交换的缓冲区(称作Cache [ kæʃ ] ),是存贮数据的临时地方,一般读写性能较高

2.1.1 缓存的应用场景

  缓存的应用场景:浏览器缓存、应用层缓存(如Redis)、数据库缓存(如:索引)、CPU多级缓存、磁盘缓存

  • 浏览器缓存:主要是存在于浏览器端的缓存

  • 应用层缓存:可以分为tomcat本地缓存,比如之前提到的map,或者是使用redis作为缓存

  • 数据库缓存:在数据库中有一片空间是 buffer pool,增改查数据都会先加载到mysql的缓存中

  • CPU缓存:当代计算机最大的问题是 cpu性能提升了,但内存读写速度没有跟上,所以为了适应当下的情况,增加了cpu的L1,L2,L3级的缓存

在这里插入图片描述

  CPU多级缓存的诞生:在计算机中,主要的构造为CPU、内存、磁盘。由于CPU的运算能力随着科技的发展,其计算能力已经远远的超过内存和磁盘的读写数据的能力,但是CPU所做的任何运算都需要从内存或者磁盘中读到数据,再放到自己的寄存器里,才可以进行运算。正是由于这种数据读写的能力远远低于CPU的运算能力,导致计算机性能受到瓶颈。所以人们在CPU的内部添加了缓存,即CPU会把经常需要读写的数据放入CPU缓存中,当进行高速运算的时候,就不需要每次都去内存或磁盘中读取数据再运算,而是直接从缓存中获取数据直接运算,这样就可以充分释放CPU的运算能力。

  缓存的常见使用示例

1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 本地用于高并发

例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 用于redis等缓存

例3:Static final Map<K,V> map =  new HashMap(); 本地缓存

由于其被Static修饰,所以随着类的加载而被加载到内存之中,作为本地缓存,由于其又被 final 修饰,所以其引用 (例3:map) 和对象 (例3:new HashMap()) 之间的关系是固定的,不能改变,因此不用担心赋值(=)导致缓存失效。

2.1.2 为什么要使用缓存

一句话:因为速度快,好用

  缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力

  实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大的数据量,如果没有缓存来作为"避震器",系统是几乎是撑不住的,所以企业会大量运用到缓存技术。

2.1.3 Web应用中缓存的作用

缓存的作用

  1️⃣ 降低后端负载:请求先进入缓存中查找数据,若缓存中不存在再将请求向数据库发送,大大降低后端数据库压力。

  2️⃣ 提高读写效率,降低响应时间

2.1.4 Web应用中缓存的成本

缓存的成本

  1️⃣ 数据一致性成本:数据本身只保存在数据库,现在将数据缓存了一份放到了内存中(如Redis),如果数据库中的数据发生了变化而缓存中的数据仍然是旧数据,由于请求先进入缓存中查找数据,就会造成数据的不一致性。

  2️⃣ 代码维护成本:由于要保证数据一致性,自然会增加业务编码,且会出现缓存穿透、雪崩、击穿等问题,会大幅度提高代码复杂度。

  3️⃣ 运维成本:为了避免缓存雪崩或缓存的高可用,需要搭建成缓存集群模式,提高了运维成本。

2.2 添加Redis缓存

2.2.1 背景

  给访问MySQL数据库的接口添加缓存,提高查询性能。

  在我们查询商户信息时,资料中ShopController类的 queryShopById 方法,是调用 MyBatisPlus 的 getById 方法,从对应数据表通过主键 id 查询数据的方法。由于该方法是直接操作从数据库中去进行查询的,现在我们对其增加一层缓存,提高该接口的查询效率。

    /**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return Result.ok(shopService.getById(id)); // 目前是直接查询数据库
    }
2.2.2 缓存模型和思路

瑞:遇事不决,加一层

  未添加缓存前的逻辑:客户端向服务器发起的请求,会直接发送到数据库,通过数据库查询后,将结果返回给客户端,如下图

在这里插入图片描述

  添加缓存就相当于,在客户端和数据库之间添加了中间层(如Redis缓存),这样客户端的请求就有限到达缓存(Redis),如果Redis中有该查询结果,则直接返回,就不会到达数据库,这样数据库压力就大幅度减轻了。若Redis中无该查询结果(未命中),再将该请求发送至数据库,数据库将查询结果返回给客户端并写入缓存中

在这里插入图片描述

  相对应的业务流程也要修改,如下图

在这里插入图片描述

2.2.3 代码实现

  主要是改进ShopController类的 queryShopById 方法,当前代码如下:

    /**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return Result.ok(shopService.getById(id));
    }

  1️⃣ 将ShopController类的 queryShopById 方法的业务搬至 Service 层中的自定义 queryByI d方法

    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return shopService.queryById(id);
    }

  2️⃣ IShopService 接口创建 queryById 方法

public interface IShopService extends IService<Shop> {

    Result queryById(Long id);
}

  3️⃣ 在ShopServiceImpl实现类中实现 queryById 方法

   queryById 实现思路:如果缓存有,则直接返回,如果缓存不存在,则查询数据库,然后存入redis

  3️⃣➖1️⃣ 先注入StringRedisTemplate

    @Resource
    private StringRedisTemplate stringRedisTemplate;

  3️⃣➖2️⃣ RedisConstants中加入常量

    public static final String CACHE_SHOP_KEY = "cache:shop:";

  3️⃣➖3️⃣ 实现 queryById


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisConstants;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        // 1.从Redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)){
            // 3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson,Shop.class);
            return Result.ok(shop);
        }
        // 4.不存在,根据id查询数据库
        Shop shop = getById(id);
        // 5.不存在,返回错误
        if (shop == null){
            return Result.fail("商铺不存在!");
        }
        // 6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        // 7.返回
        return Result.ok(shop);
    }
}
2.2.4 测试

  1️⃣ 重启后端服务,登录账户,进入首页,点击美食,选择103茶餐厅,查看请求状态

在这里插入图片描述

  2️⃣ 打开Redis客户端查看数据是否存入缓存中

在这里插入图片描述

  3️⃣ 清空后端控制台,重新刷新该页面发送请求,检测该请求是否不再访问数据库(没有输出商品查询SQL日志)

在这里插入图片描述

瑞:控制台此时只输出 VoucherMapper.queryVoucherOfShop查询优惠券的SQL日志,而没有输出ShopMapper.selectById的SQL日志,证明该请求已被缓存拦截

附:IDEA控制台输出自动换行设置

File ➡️Settings… ➡️ Editor ➡️ General ➡️ Console ➡️ Use soft wraps in console ➡️ 勾选 ➡️ Apply

在这里插入图片描述

2.3 缓存更新策略

瑞:缓存是一个双刃剑,带来好处的同时也导致了数据一致性等问题,缓存的更新策略就是为了解决这个问题

  由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在

2.3.1 常见的缓存更新三大策略

  缓存更新是redis为了节约内存而设计出来的一个东西,主要是因为内存数据宝贵,当我们向redis插入太多数据,此时就可能会导致缓存中的数据过多,所以redis会对部分数据进行更新,或者把他叫为淘汰更合适。

  常见的缓存更新策略见下表⬇️

内存淘汰超时剔除主动更新
说明不用自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存给缓存数据添加TTL时间,到期后自动删除缓存。下次查询时更新缓存编写业务逻辑,在修改数据库的同时,更新缓存
一致性一般
维护成本

业务场景

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

  内存淘汰:redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以自己设置策略方式)

2.3.1.2 超时剔除

  超时剔除:当我们给redis设置了过期时间ttl之后,redis会将超时的数据进行删除,方便咱们继续使用缓存

2.3.1.3 主动更新

  主动更新:我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不一致问题

2.3.2 主动更新策略——数据库缓存不一致的解决方案

  主动更新策略的三种模式

  1️⃣ Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案

瑞:该模式在大多数场景中被采用,所以可以认为:数据库和缓存不一致采用的是双写方案。但双写方案需要注意下一节提到的三个问题。

  2️⃣ Read/Write Through Pattern : 缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。由系统本身完成,数据库与缓存的问题交由系统本身去处理

瑞:该模式开发成本高

  3️⃣ Write Behind Caching Pattern 写回:调用者只操作缓存,其他线程去异步处理数据库,实现最终一致

瑞:该模式的好处在于,频繁的读写操作在缓存中进行,将多次的读写转化为1次,有效降低了数据库压力,相当于数据库的缓冲区。但问题比较多:该异步任务的开发困难;以及数据一致性难以保证,任务间隔中缓存和数据库的数据不一致;并且可靠性也存在问题,如果任务期间缓存服务器宕机,则可能会导致数据丢失

2.3.3 双写方案的三个注意事项

  综合考虑使用方案1️⃣Cache Aside Pattern,但是方案1️⃣的调用者需要思考操作缓存和数据库的以下三个问题

2.3.3.1 删除缓存
  • 1️⃣ 删除缓存还是更新缓存?
    • ❌ 更新缓存:每次更新数据库都更新缓存,无效写操作较多 ❌
    • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存

如果采用第一个方案,那么假设我们每次操作数据库后,都操作缓存,但是中间如果没有人查询(写多读少),那么这个更新动作实际上只有最后一次生效,中间的更新动作意义并不大,所以我们可以把缓存删除,等待再次查询时,将缓存中的数据加载出来

2.3.3.2 保证缓存与数据库的操作的同时成功或失败
  • 2️⃣ 如何保证缓存与数据库的操作的同时成功或失败?
    • 单体系统,将缓存与数据库操作放在一个事务 ✅
    • 分布式系统,利用TCC等分布式事务方案 ✅

瑞:分布式系统中也可以使用消息队列等方案处理

2.3.3.3 先操作数据库再删除缓存
  • 3️⃣ 先操作缓存还是先操作数据库?
    • ❌ 先删除缓存,再操作数据库 ❌
    • 先操作数据库,再删除缓存

瑞:为尽量保证数据一致性,我们应当先操作数据库,再删除缓存。虽然两种方案都有可能会造成数据不一致性的问题,但方案二发生的概率远远小于方案一,且方案二的数据不一致问题容易得到解决。

  下图为:先删除缓存,再操作数据库的正常情况

在这里插入图片描述

  先删除缓存,再操作数据库。在正常情况下好像没问题,但在多线程环境下不安全,由于删缓存、查缓存、查数据库的操作较快(相对),而更新数据库即写操作较慢(相对),所以很容易在过程中被其它线程重新写入缓存,造成数据不一致的问题

  下图为:先删除缓存,再操作数据库的异常情况

在这里插入图片描述

假设此时数据库是存储10,需要更新为20,在两个线程并发来访问时,线程1先来,线程1会先把缓存删了,此时线程2过来,他查询缓存数据并不存在(因为线程1并未更新数据库的值,线程1此刻只删除了缓存值10),由于线程2未命中,则会查询数据库(旧值10),此时他写入缓存(旧值10),当线程2写入缓存后,线程1再执行更新动作,把数据库的值改为了20,导致此时数据库的值为20,而缓存的值为10,数据不一致❗️ ❗️ ❗️


  下图为:先操作数据库,再删除缓存的正常情况

在这里插入图片描述

  先操作数据库,再删除缓存。在正常情况下也没问题,但在多线程环境下仍然可能不安全,但相对情况一要好很多。因为:首先线程1来时恰好缓存失效的概率低、其次在线程1查询缓存恰好失效的情况下,线程1查询到数据库的值之后,在更新缓存这微妙级别的时间范围内,突然来一个线程2,先更新数据库(较慢)然后线程2删除缓存,这么多操作要在微妙的时间内完成,才会造成数据不一致的问题,同时满足这三个巧合的概率相对低。且如果发生这种情况,只要加上超时时间即可有效解决

  下图为:先操作数据库,再删除缓存的异常情况

在这里插入图片描述

假设此时数据库是10,由于某些原因,恰好缓存失效,线程1来查,则一定未命中,需要查询数据库,得到10,由于线程1未命中,要把10写入缓存,此时,线程2来更新数据库(将数据库修改为20),线程2在更新完数据库后再删除缓存,但此时缓存中其实已经失效,所以删除操作等于没删,然后线程1(得到的数据库是旧值10)将10写入数据库,导致了数据不一致❗️但这种概率极低,且如果万一发生了,只要加上超时时间,由于数据库的数据是正确的,过一段时间缓存便会同步,容易解决

2.3.4 缓存更新策略的最佳实践方案

  1️⃣ 低一致性需求:使用Redis自带的内存淘汰机制

  2️⃣ 高一致性需求:主动更新,并以超时剔除作为兜底方案
    2️⃣➖1️⃣ 读操作:
      2️⃣➖1️⃣➖1️⃣ 缓存命中则直接返回
      2️⃣➖1️⃣➖2️⃣ 缓存未命中则查询数据库,并写入缓存,设定超时时间
    2️⃣➖2️⃣ 写操作:
      2️⃣➖2️⃣➖1️⃣ 先写数据库,然后再删除缓存
      2️⃣➖2️⃣➖2️⃣ 要确保数据库与缓存操作的原子性

2.3.5 案例:添加超时剔除和主动更新策略

ShopController 代码回顾(点我跳转)

2.3.5.1 需求

  修改代码中的ShopController中的业务逻辑,满足下面的需求:

  1️⃣ 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间(超时剔除)

  2️⃣ 根据id修改店铺时,先修改数据库,再删除缓存(主动更新)

2.3.5.2 代码实现

  1️⃣ 实现超时剔除

  1️⃣➖1️⃣ 修改ShopServiceImpl的 queryById 方法的业务逻辑,设置超时时间stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisConstants;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        // 1.从Redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        // 4.不存在,根据id查询数据库
        Shop shop = getById(id);
        // 5.不存在,返回错误
        if (shop == null) {
            return Result.fail("商铺不存在!");
        }
        // 6.存在,写入Redis,并设置超时时间(超时剔除)
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        // 7.返回
        return Result.ok(shop);
    }
}

在这里插入图片描述

(此图为未实现超时剔除前的代码)

  2️⃣ 实现主动更新

  2️⃣➖1️⃣ 修改ShopController的 updateShop 方法的业务逻辑

    /**
     * 更新商铺信息
     * @param shop 商铺数据
     * @return 无
     */
    @PutMapping
    public Result updateShop(@RequestBody Shop shop) {
        // 写入数据库
        return shopService.update(shop);
    }

在这里插入图片描述

(此图为未实现主动更新前的代码)

  2️⃣➖2️⃣ IShopService接口中添加 update 方法

public interface IShopService extends IService<Shop> {

    Result queryById(Long id);

    Result update(Shop shop);
}

  2️⃣➖3️⃣ ShopServiceImpl实现类中实现 update 方法


    @Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        // 1.更新数据库
        updateById(shop);
        // 2.删除缓存
        stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + id);
        return Result.ok();
    }

瑞:要注意事务,本例为单体项目中事务的处理,如果是分布式 \ 微服务项目,需要用消息队列通知其它服务等方式保证数据的一致性

2.3.5.3 测试

  重启后端服务

  • 测试超时剔除
      1️⃣ 删除Redis中cache:shop:1的数据,因为之前的章节中没有设置TTL,所以要将其删除
      2️⃣ 前端登录账户,进入首页,点击美食,选择103茶餐厅,查看Redis中cache:shop:1的TTL是否被设置为了1800左右(30分钟)图中显示1794是因为博主操作了6秒钟,导致过期时间不是1800

在这里插入图片描述

  • 测试主动更新
      1️⃣ 由于更新商铺信息接口 updateShop 在前端没有对普通用户直接开放,所以使用postman测试(注意请求是PUThttp://localhost:8081/shop
{
    "area": "大关",
    "openHours": "10:00-22:00",
    "sold": 4215,
    "address": "金华路锦昌文华苑29号",
    "comments": 3035,
    "avgPrice": 80,
    "socrs": 37,
    "name": "101茶餐厅",
    "typeId": 1,
    "id": 1
}

在这里插入图片描述

    2️⃣ 在使用 postman 发送PUT更新请求后,不对页面进行其它操作,直接查看Redis客户端,刷新数据库,发现cache:shop:1已被删除,说明主动更新代码执行成功

在这里插入图片描述

    3️⃣ 对前端餐厅详情页面进行刷新,即访问ShopController的 queryShopById 方法,发现前端信息更新成功,是数据库中更新的名字(103 修改为了 postman发送的请求,即改为了101),且Redis客户端中存储了cache:shop:1的数据,并且半小时后该数据会被自动删除(不对该接口进行访问的前提下),说明超时剔除和主动更新实现成功

在这里插入图片描述

2.3.6 代码资源

hm-dianping【2.3缓存更新策略】后端代码的某度网盘链接如下,需要请自取

链接:https://pan.baidu.com/s/1D06xA2IU6Atfp9fZeygpNA?pwd=dwi9 
提取码:dwi9

2.4 缓存穿透

瑞:请参考《瑞_Redis_商户查询缓存》

2.5 缓存雪崩

瑞:请参考《瑞_Redis_商户查询缓存》

2.6 缓存击穿

瑞:请参考《瑞_Redis_商户查询缓存》

2.7 缓存工具封装

瑞:请参考《瑞_Redis_商户查询缓存》




本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


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

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

相关文章

阿里云2核2G和2核4G轻量应用服务器优惠价格表,2024年最新报价

阿里云轻量应用服务器2核2G和2核4G配置优惠价格表&#xff0c;轻量2核2G3M带宽61元一年&#xff0c;轻量2核4G4M带宽165元1年&#xff0c;均不限制月流量&#xff0c;阿里云活动链接 aliyunfuwuqi.com/go/aliyun 活动打开如下图&#xff1a; 阿里云轻量应用服务器价格 61元/年…

《QT实用小工具·七》CPU内存显示控件

1、概述 源码放在文章末尾 CPU内存显示控件 项目包含的功能如下&#xff1a; 实时显示当前CPU占用率。实时显示内存使用情况。包括共多少内存、已使用多少内存。全平台通用&#xff0c;包括windows、linux、ARM。发出信号通知占用率和内存使用情况等&#xff0c;以便自行显示…

ubuntu-server部署hive-part1-安装jdk

参照 https://blog.csdn.net/qq_41946216/article/details/134345137 操作系统版本&#xff1a;ubuntu-server-22.04.3 虚拟机&#xff1a;virtualbox7.0 安装jdk 上传解压 以root用户&#xff0c;将jdk上传至/opt目录下 tar zxvf jdk-8u271-linux-x64.tar.gz 配置环境变量…

2_6.Linux高级存储管理

##1.逻辑卷## pv ##物理卷 被处理过的物理分区 pe ##物理扩展 设定存储最小单元 vg ##物理卷组 捆绑pv到一个组中 lv ##逻辑卷 分配最终的使用设备 监控建立过程&#xff1a; watch -n 1 "pvs;echo ;vgs;echo ;lvs;echo ;df -h /weixindata" &#xff08;1&#xf…

【C++ STL有序关联容器】map 映射

文章目录 【 1. 基本原理 】【 2. map 的创建 】2.1 调用默认构造函数&#xff0c;创建一个空的 map2.2 map 被构造的同时初始化2.3 通过一个 queue 初始化另一个 queue2.4 取已建 map 中指定区域内的键值对&#xff0c;初始化新的 map2.5 指定排序规则 【 2. map 元素的操作 】…

知识融合:知识图谱构建的关键技术

目录 一、引言二、知识图谱基础2.1 知识表示三元组属性图 2.2 知识抽取实体抽取关系抽取属性抽取 三、知识融合的核心问题3.1 实体识别与链接实体识别实体链接 3.2 重复实体合并方法示例 3.3 关系融合挑战方法示例 四、知识融合技术深度解析4.1 基于规则的方法规则设计原则规则…

噪声的力量:重新定义 RAG 系统的检索

该文得到了一个反常识的结论&#xff0c;当无关的噪声文档放在正确的位置时&#xff0c;实际上有助于提高RAG的准确性。 摘要 检索增强生成&#xff08;RAG&#xff09;系统代表了传统大语言模型&#xff08;大语言模型&#xff09;的显着进步。 RAG系统通过整合通过信息检索…

Redission--布隆过滤器解决缓存穿透问题

布隆过滤器在缓存穿透问题中的使用 布隆过滤器的核心是一个位数组 布隆过滤器的误判 使用Redission的布隆过滤器步骤 添加 Redission 依赖&#xff1a;首先需要将 Redission 添加到你的 Java 项目中&#xff0c;你可以通过 Maven 来添加 Redission 的依赖。 创建 Redissio…

我的C++奇迹之旅:内联函数和auto关键推导和指针空值

文章目录 &#x1f4dd;内联函数&#x1f320; 查看内联函数inline方式&#x1f309;内联函数特性&#x1f309;面试题 &#x1f320;auto关键字(C11)&#x1f320; auto的使用细则&#x1f309;auto不能推导的场景 &#x1f320;基于范围的for循环(C11)&#x1f320;范围for的…

Spatio-Temporal Pivotal Graph Neural Networks for Traffie Flow Forecasting

摘要:交通流量预测是一个经典的时空数据挖掘问题,具有许多实际应用。,最近,针对该问题提出了各种基于图神经网络(GNN)的方法,并取得了令人印象深刻的预测性能。然而,我们认为大多数现有方法忽视了某些节点(称为关键节点)的重要性,这些节点自然地与多个其他节点表现出…

IPSEC VPN双机热备份的配置讲解一

IPSEC VPN双机热备份的配置讲解一 VPN 是一种专用网络&#xff0c;可使用公共网络连接两个或两个以上的远程站点。VPN 可使用通过公共网络路由&#xff08;以隧道方式发送&#xff09;的虚拟连接&#xff0c;而非网络之间的专用连接。IPsec VPN 是一项协议&#xff0c;由建立 …

使用神经网络-遗传算法优化神经网络-风电预测故障(BP,GABP,matlab)

本项目是故障预测&#xff0c;不是时序预测&#xff0c;本质还是分类问题 1 数据集介绍 特征文件&#xff1a; 标签文件&#xff1a;共计4个标签&#xff0c;其中大多数都是正常的&#xff0c;其他是3个不正常的类别 2 使用BP网络 2.1 读取数据&#xff0c;然后选择几个…

迷茫下是自我提升

长夜漫漫&#xff0c;无心睡眠。心中所想&#xff0c;心中所感&#xff0c;忧愁当前&#xff0c;就执笔而下&#xff0c;写下这篇文章。 回忆过往 回想当初为啥学前端&#xff0c;走前端这条路&#xff0c;学校要求嘛&#xff0c;兴趣爱好嘛&#xff0c;还是为了钱。 时间带着…

【PostgreSQL】技术传承:使用Docker快速部署PostgreSQL数据库

前言 PostgreSQL的重要贡献者Simon Riggs因一起坠机事故不幸离世。Simon Riggs是英国著名的软件与服务领导者&#xff0c;也是PostgreSQL的主要开发者和贡献者。事故发生在英国当地时间3月26日13:41分&#xff0c;当时他驾驶的私人通用航空Cirrus SR22飞机在英国达克斯福德机场…

HTTP详解及代码实现

HTTP详解及代码实现 HTTP超文本传输协议 URL简述状态码常见的状态码 请求方法请求报文响应报文HTTP常见的HeaderHTTP服务器代码 HTTP HTTP的也称为超文本传输协议。解释HTTP我们可以将其分为三个部分来解释&#xff1a;超文本&#xff0c;传输&#xff0c;协议。 超文本 加粗样…

基于Unet的BraTS 3d 脑肿瘤医学图像分割,从nii.gz文件中切分出2D图片数据

1、前言 3D图像分割一直是医疗领域的难题&#xff0c;在这方面nnunet已经成为了标杆&#xff0c;不过nnunet教程较少&#xff0c;本人之前跑了好久&#xff0c;一直目录报错、格式报错&#xff0c;反正哪里都是报错等等。并且&#xff0c;nnunet对于硬件的要求很高&#xff0c…

基于51单片机电子钟闹钟设计

基于51单片机电子钟闹钟设计 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff09; 功能介绍 具体功能&#xff1a; 1.LCD1602实时显示时间和闹钟时间&#xff1b; 2.可整点报时。 3.按键设置时间、闹钟。 4.使用蜂鸣器报时和响闹钟&#xff1b; ​演示视频&…

CCIE-14-MPLS_and_BGP

目录 实验条件网络拓朴 环境配置开始配置配置MPLSR1访问R6检测结果R6访问R1检测结果 实验条件 网络拓朴 环境配置 在我的资源里可以下载&#xff08;就在这篇文章的开头也可以下载&#xff09; 开始配置 R1<->R2&#xff1a;EBGP R2<->R5&#xff1a;IBGP&…

Azkaban集群模式部署详细教程

序言 Azkaban是一个用于工作流程调度和任务调度的开源工具&#xff0c;它可以帮助用户轻松地管理和监控复杂的工作流程。Azkaban的架构设计旨在提供高度可扩展性和可靠性&#xff0c;同时保持易用性和灵活性。 Azkaban的架构可以分为三个主要组件:Executor、Web Server和db数据…

FebHost:什么是土耳其.TR域名?

当前互联网高速发展,一个国家的顶级域名已成为其网络形象的重要标识。近期,土耳其国家顶级域名”.TR”引起了广泛关注,成为业界热议的话题。 作为代表土耳其共和国的国家顶级域名(ccTLD),.TR域名于1991年首次引入,由土耳其科技和信息技术部负责管理。除了常见的”.com.tr”、”…