③使用Redis缓存,并增强数据一致性。

在这里插入图片描述

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~

在这里插入图片描述

使用Redis缓存,并增强数据一致性。

  • Redis缓存
    • 🚀为什么使用缓存?
    • 🚀如何添加Redis缓存?
    • 🚀缓存数据一致性问题(双写问题)
    • 🚀实现 缓存与数据库双写一致(此方式不能保证绝对一致)


Redis缓存


🚀为什么使用缓存?

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

  • 缓存的作用:
    • 降低后端负载。
    • 提高读写效率,降低响应时间。

使用缓存的同时,也会增加代码复杂度和运营的成本。

  • 缓存的成本:
    • 数据一致性成本(双写问题)
    • 代码维护成本
    • 运维成本

  • 缓存的使用案例:

    • 缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码(例如:

      // 例1:本地用于高并发
      Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 
      
      //例2:用于redis等缓存
      static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 
      
      //例3:本地缓存
      Static final Map<K,V> map =  new HashMap(); 
      

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




🚀如何添加Redis缓存?

Redis缓存作用模型

标准的操作方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。

在这里插入图片描述



为查询的数据添加缓存 业务逻辑

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 根据id查询商铺信息
    @Override
    public Result queryById(Long id) {
        // redis缓存的key
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        //1. 从redis缓存中获取shop信息
        String shopJSON = stringRedisTemplate.opsForValue().get(key);

        //2. 缓存存在,返回(Hutool工具:StrUtil、JSONUtil)
        if(StrUtil.isNotBlank(shopJSON)){
            Shop shop = JSONUtil.toBean(shopJSON, Shop.class);
            return Result.ok(shop);
        }

        //3. 缓存未命中,从数据库中获取
        Shop shop = this.getById(id);

        //4. 数据库中不存在,返回错误
        if(shop == null) return Result.fail("店铺不存在!");

        //5. 数据库中存在,存入redis缓存(Hutool工具:JSONUtil)
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));

        //6. 返回
        return Result.ok(shop);
    }



🚀缓存数据一致性问题(双写问题)

双写问题

双写问题通常出现在以下场景:

  1. 写入数据源: 应用程序接收到写入请求后,首先将数据写入主要的数据源(例如数据库)。
  2. 写入缓存: 同时,应用程序尝试将相同的数据写入缓存,以提高后续对该数据的读取性能。

在这个过程中,如果写入数据源成功而写入缓存失败,或者写入缓存成功而写入数据源失败,就会导致数据不一致的情况。例如:

  • 写入数据源成功,写入缓存失败: 在这种情况下,缓存中可能没有最新的数据,而应用程序仍然从缓存中读取旧数据,导致不一致。
  • 写入缓存成功,写入数据源失败: 这种情况下,缓存中包含了最新的数据,但是由于数据源没有更新,当应用程序从数据源中读取数据时,可能得到旧的数据,同样导致不一致。



解决方案

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

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

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



使用Cache Aside Pattern人工编码方式,需要注意的问题

  • 删除缓存还是更新缓存?
    • 更新缓存:每次更新数据库都更新缓存,无效写操作较多(×)
    • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存(

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

  • 先操作缓存还是先操作数据库?
    • 选择①:先删除缓存,再操作数据库
    • 选择②:先操作数据库,再删除缓存(
    • 应该具体操作缓存还是操作数据库? 我们应当是先操作数据库,再删除缓存 ,原因在于,如果你选择第一种方案,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。



🚀实现 缓存与数据库双写一致(此方式不能保证绝对一致)

流程

  • 查询数据时,若缓存未命中,从数据库中获取,再将结果写入缓存,设置过期时间(TTL)。
  • 修改数据时,先更新数据库,再删除缓存。



查询数据时

    // 根据id查询商铺信息
    @Override
    public Result queryById(Long id) {
        // redis缓存的key
        String key = "cache:shop:" + id;
        //1. 从redis缓存中获取shop信息
        String shopJSON = stringRedisTemplate.opsForValue().get(key);

        //2. 缓存存在,返回
        if(StrUtil.isNotBlank(shopJSON)){
            Shop shop = JSONUtil.toBean(shopJSON, Shop.class);
            return Result.ok(shop);
        }

        //3. 缓存未命中,从数据库中获取
        Shop shop = this.getById(id);

        //4. 数据库中不存在,返回错误
        if(shop == null) return Result.fail("店铺不存在!");

        //5. 数据库中存在,存入redis缓存,并设置过期时间ttl
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);

        //6. 返回
        return Result.ok(shop);
    }



查询数据时(解决缓存穿透):

    // 根据id查询商铺信息(缓存空值,避免缓存穿透问题)
    @Override
    public Result queryById(Long id) {
        // redis缓存的key
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        //1. 从redis缓存中获取shop信息
        String shopJSON = stringRedisTemplate.opsForValue().get(key);

        if(shopJSON == null){ // 获取值为空,返回错误
            return Result.fail("商铺不存在!");
        }

        //2. 缓存存在,返回
        if(StrUtil.isNotBlank(shopJSON)){
            Shop shop = JSONUtil.toBean(shopJSON, Shop.class);
            return Result.ok(shop);
        }

        //3. 缓存未命中,从数据库中获取
        Shop shop = this.getById(id);

        //4. 数据库中不存在,空值写入Redis,返回错误
        if(shop == null){
            // 控制写入Redis,设置2分钟有效期
            stringRedisTemplate.opsForValue().set(key, "", 2L, TimeUnit.MINUTES);
            //返回错误
            return Result.fail("商铺不存在!");
        }

        //5. 数据库中存在,存入redis缓存
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);

        //6. 返回
        return Result.ok(shop);
    }



修改数据时

    @Override
    @Transactional  //开启事务,保证证缓存与数据库操作同时成功或失败
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id == null) return Result.fail("商铺ID不能为空!");
        
        //注意: 先更新数据库再删除缓存
        
        //1. 更新数据库
        this.updateById(shop);

        //2. 删除缓存
        stringRedisTemplate.delete("cache:shop:" + id);

        return Result.ok();
    }




在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1. 认识SPSS

使用的是IBM SPSS statistics 25&#xff0c;参考教材《统计分析与SPSS的应用》 一、安装和启动 具体安装过程是参考spss下载以及安装详细教程这篇文章&#xff0c;下载安装包然后按他的步骤获取用户许可证即可。 二、主要窗口 数据编辑器窗口data editor 是SPSS的主程序窗…

UV映射技巧和窍门

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 有一个鲜为人知的主题是纹理映射。这始终是 3D 建模师想要处理的最后…

.NET 6中如何使用Redis

1、安装redis Redis在windows平台上不受官方支持&#xff0c;所以想要在window安装Redis就必须去下载windows提供的安装包。安装地址&#xff1a;https://github.com/tporadowski/redis/releases 2、在NueGet安装包 3、在appsettings.json文件里面添加Redis相关配置信息 &quo…

第11章 GUI Page480~486 步骤二十七 “脏数据”与“新文档”状态维护

wxMyPainterFrame类定义中声明新的成员&#xff1a; 增加一个全局变量&#xff0c;初始化新成员&#xff1a; 先实现TrySaveFile() SaveFile()暂时为空实现 增加两个新的私有成员方法&#xff1a; wxMyPainterFrame类中&#xff0c;修改了“_items”的几个地方 ① 鼠标抬起时…

uniapp中使用tmt-calendar字体的颜色如何修改

tmt-calendar这个插件市场下载的组件默认的眼色为深蓝&#xff0c;如下图 然后能够动态修改这些颜色的参数也很限制&#xff0c;就想着从源代码上面去修改&#xff0c; 但是本人在项目的src/components这个目录下找了很久没有找到 又去node_modules目录下寻找也没有找到&#…

嵌入式——循环队列

循环队列 (Circular Queue) 是一种数据结构(或称环形队列、圆形队列)。它类似于普通队列,但是在循环队列中,当队列尾部到达数组的末尾时,它会从数组的开头重新开始。这种数据结构通常用于需要固定大小的队列,例如计算机内存中的缓冲区。循环队列可以通过数组或链表实现,…

【深度学习】优化器介绍

文章目录 前言一、梯度下降法&#xff08;Gradient Descent&#xff09;二、动量优化器&#xff08;Momentum&#xff09;三、自适应学习率优化器 前言 深度学习优化器的主要作用是通过调整模型的参数&#xff0c;使模型在训练数据上能够更好地拟合目标函数&#xff08;损失函…

Fiddler工具 — 9.命令行和状态栏

1、命令行 命令行在Fiddler的左下方的黑色窗口&#xff0c;也叫QuickExec&#xff0c;可以调用 Fiddler的内置命令。 这一系列内置的函数用于筛选和操作会话列表中的session&#xff08;会话&#xff09;。 虽然它不是很显眼&#xff0c;但用好它&#xff0c;会让你的工作效率…

python 函数中字典的修改会影响函数外字典的值

def modify_dict(d):d[key] new valueprint(函数中字典d的位置,id(d))# 创建一个字典 original_dict {key: old value} print(函数外字典的位置,id(original_dict))# 调用函数来修改字典 modify_dict(original_dict)# 输出原始字典的值&#xff0c;可以看到它已经被修改了 pr…

致远OA getAjaxDataServlet XXE漏洞复现(QVD-2023-30027)

0x01 产品简介 致远互联-OA 是数字化构建企业数字化协同运营中台,面向企业各种业务场景提供一站式大数据分析解决方案的协同办公软件。 0x02 漏洞概述 致远互联-OA getAjaxDataServlet 接口处存在XML实体注入漏洞,未经身份认证的攻击者可以利用此漏洞读取系统内部敏感文件…

基于Spark个性化图书推荐系统

介绍 该系统基于Spark&#xff0c;结合了协同过滤算法和个性化推荐技术&#xff0c;实现了一款个性化的书籍推荐系统。 在该系统中&#xff0c;用户可以通过登陆注册后进入系统&#xff0c;查找和筛选自己喜欢的图书信息&#xff0c;同时也能够获得基于用户历史浏览、评分等数…

【PostgreSQL在线创建索引(CIC)功能的锁分析以及使用注意】

前一篇文章提到了普通创建索引会阻塞DML操作 PostgreSQL创建索引的锁分析和使用注意 而PostgreSQL里可以使用create index concurrently 在线创建索引(CIC)功能&#xff0c;降低创建索引在表上申请的锁的级别&#xff0c;ShareUpdateExclusiveLock级别的锁和RowExclusiveLock…

解决Qt Creator中文乱码的问题

方法1 使用QStringLiteral()包裹中文字符串 QString str1"中文测试&#xff01;"; QString str2QStringLiteral("中文测试&#xff01;");方法2 #if _MSC_VER > 1600//MSVC2015>1899,MSVC_VER14.0 #pragma execution_character_set("utf-8&qu…

第二百五十四回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何给图片添加阴影"相关的内容&#xff0c;本章回中将介绍自定义Radio组件.闲话休提&#xff0c;让我们一起Talk Flutter吧…

7个Pandas绘图函数助力数据可视化

大家好&#xff0c;在使用Pandas分析数据时&#xff0c;会使用Pandas函数来过滤和转换列&#xff0c;连接多个数据帧中的数据等操作。但是&#xff0c;生成图表将数据在数据帧中可视化&#xff0c;通常比仅仅查看数字更有帮助。 Pandas具有几个绘图函数&#xff0c;可以使用它…

Java面向对象综合练习(拼图小游戏),用java图形化界面实现拼图小游戏

1. 设计游戏的目的 锻炼逻辑思维能力利用Java的图形化界面&#xff0c;写一个项目&#xff0c;知道前面学习的知识点在实际开发中的应用场景 2. 游戏的最终效果呈现 Hello&#xff0c;各位同学大家好。今天&#xff0c;我们要写一个非常有意思的小游戏 —《拼图小游戏》 我们…

【机器学习】循环神经网络(三)

四、序列预测问题 循环神经网络实现的序列到序列的映射&#xff08;Recurrent Neural Network based Sequence-to-Sequence Mapping&#xff09;是一种使用循环神经网络来将一个序列数据映射到另一个序列数据的方法&#xff0c;它可以用于机器翻译、文本摘要、对话生成等任务。…

多国管理中心多语言区块链源码一元夺宝程序仿趣步奕跑/原生计步器/原生人脸识别

前后台分开的&#xff0c;后台是TP3.2的框架了&#xff0c;应该是比较老的程序了。 目前把整体UI 改版黄色系风格&#xff0c;集成了一元夺宝程序&#xff0c;用户数据同步趣步&#xff0c;效果看起来很棒&#xff0c;另外加入股票走势图&#xff08;K线图&#xff09;&#xf…

使用即时设计绘制原型设计方便吗?和Axure RP相比怎么样?

对于原型设计&#xff0c;APP 和 Web 都是一样的&#xff0c;因为产品原型是用来确定需求的工具。我们使用这种工具的目的是为了快速迭代&#xff0c;从而深入挖掘和筛选产品的需求。 绘制原型&#xff0c;最重要的原则是&#xff1a;快速、清晰&#xff01; Axure 工具的优缺…

“单项突出”的赢双科技IPO加速,比亚迪是最强助力?

近日&#xff0c;新能源汽车核心部件供应商赢双科技首次递表科创板&#xff0c;其凭借旋转变压器产品就坐稳了新能源车企主要供应商的地位&#xff0c;从核心业务及业绩情况来看&#xff0c;赢双科技不愧为“单项冠军”。 据悉&#xff0c;赢双科技本次IPO拟募资8.47亿元&…