Redis实战——商户查询(一)

news2024/11/28 12:52:40

商户查询

  • 缓存(Cache):就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,缓存数据在内存中,内存的读写性能完全高于磁盘,使用缓存可以大大降低用户访问并发量带来的服务器读写压力。当数据量较大时,如果没有缓存来作为“避震器(防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪)”,系统很难支撑。

数据库直接查询

  • 在没有缓存时,查询商户信息,我们直接操作从数据库中去进行查询,但是从数据库中查询肯定是个耗时操作。如下通过id在数据库中查询商铺。

    • controller层
    @RestController
    @RequestMapping("/shop")
    public class ShopController {

        @Resource
        public IShopService shopService;

        /**
         * 根据id查询商铺信息
         * @param id 商铺id
         * @return 商铺详情数据
         */

        @GetMapping("/{id}")
        public Result queryShopById(@PathVariable("id") Long id) {
            Shop shop = shopService.getShopById(id);
            if (ObjectUtil.isNull(shop)){
                return Result.fail("商铺不存在");
            }
            return Result.ok(shop);
        }
    }    
    • service层
    public interface IShopService extends IService<Shop{

        Shop getShopById(Long id);
    }
    @Service
    public class ShopServiceImpl extends ServiceImpl<ShopMapperShopimplements IShopService {


        @Override
        public Shop getShopById(Long id) {
            return this.getById(id);
        }
    }

缓存查询

  • 缓存模型和思路

    • 客户端查询数据
      • 先在缓存中查询
        • 缓存中存在,从缓存中返回
        • 缓存中不存在。查询数据库,写入缓存并返回
缓存作用模型
缓存作用模型
  • 根据Id查询商铺信息
根据id查询商铺信息-redis
根据id查询商铺信息-redis
  • service层

    @Service
    public class ShopServiceImpl extends ServiceImpl<ShopMapperShopimplements IShopService {

        @Resource
        StringRedisTemplate stringRedisTemplate;

        @Override
        public Shop getShopById(Long id) {
            //组装redis中的key
            String cacheShopKey = CACHE_SHOP_KEY + id;
            //根据ID在redis中查询商铺信息
            String shopString = stringRedisTemplate.opsForValue().get(cacheShopKey);
            //redis中查询到商铺信息
            if (StrUtil.isNotBlank(shopString)){
                Shop shop = BeanUtil.toBean(shopString, Shop.class);
                return shop;
            }
            //根据商铺id查询商铺信息
            Shop shop = this.getById(id);
            //数据库中没查询到该商铺信息
            if (ObjectUtil.isNull(shop)){
                return null;
            }
            //数据库中查询到了该商铺信息
            stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop));
            //返回给商铺信息
            return shop;
        }
    }
  • 增加相关常量

    /**
     * redis中缓存商铺信息
    */

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

缓存更新

  • 缓存更新是reids为了节约内存而设计的,主要是因为内存数据宝贵,当向redis插入太多数据,可能会导致缓存中的数据过多,所有redis会对部门数据进行更新(也许叫淘汰更合适)。

    • 内存淘汰:redis自动更新,当redis内存叨叨我们设定的max-memery时,会自动出发淘汰机制,淘汰掉一些不重要的数据化(二阳自己设置策略方式)
    • 超时剔除:为redis存储的数据设置过期时间(TTL),redis会将超时的数据进行删除
    • 主动更新:活动调用方法删除缓存,通常用于解决缓存和数据库不一致问题
策略内存淘汰超时剔除主动更新
说明redis利用redis的内存淘汰机制自动维护,当内存不足时,自动淘汰部分数据,下次查询时更新缓存为redis数据添加TTL时间,到期后redis自动删除,下次查询时更新缓存开发人员编写业务逻辑,在修改数据库的同时,更新缓存。
一致性一般
维护成本
  • 使用场景

    • 低一致性需求:使用内存淘汰机制。例如商铺类型查询缓存

      • 查询商铺类型信息

        查询商铺类型信息-redis
        查询商铺类型信息-redis
        public interface IShopTypeService extends IService<ShopType{

            List<ShopType> queryTypeList();
        }
        @Service
        public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapperShopTypeimplements IShopTypeService {

            @Autowired
            private StringRedisTemplate stringRedisTemplate;
            @Override
            public List<ShopType> queryTypeList() {
                //从redis中获取缓存数据
                Long size = stringRedisTemplate.opsForList().size(CACHE_SHOP_TYPE_KEY);
                //从redis中能够获取商铺类型数据
                if (size > 0){
                    List<String> shopTypeListStr = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_KEY, 0, size);
                    //将字符串类型转换为ShopType对象
                    List<ShopType> shopTypeList = shopTypeListStr.stream().map(shopTypeStr -> JSONUtil.toBean(shopTypeStr, ShopType.class)).collect(Collectors.toList());
                    return shopTypeList;
                }
                // 从redis中没有查询到商铺类型信息,那么去数据库中查询
                List<ShopType> shopTypeList = this.list(new LambdaQueryWrapper<ShopType>().orderByAsc(ShopType::getSort));
                // 数据库中有商铺类型信息
                if (ObjectUtil.isNotNull(shopTypeList) && shopTypeList.size() > 0){
                    //缓存到redis中
                    List<String> shopTypeJsonList = shopTypeList.stream().map(shopType -> JSONUtil.toJsonStr(shopType)).collect(Collectors.toList());
                    stringRedisTemplate.opsForList().rightPushAll(CACHE_SHOP_TYPE_KEY, shopTypeJsonList);
                    return shopTypeList;
                }
                return null;
            }
        }

        @RestController
        @RequestMapping("/shopType")
        public class ShopTypeController {
            @Resource
            private IShopTypeService typeService;

            @GetMapping("list")
            public Result queryTypeList() {
                List<ShopType> typeList = typeService.queryTypeList();
                return ObjectUtil.isNull(typeList) ? Result.fail("没有查询到商铺类型"): Result.ok(typeList);
            }
        }
    • 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询缓存

数据库缓存不一致解决方案

由于Redis缓存数据来源于数据库,当数据库中的数据发生变化时,如果当数据库中数据发生变化,Redis缓存却没有同步,此时就会出现数据一致性问题,可能会导致用户使用缓存中的过时数据,就会产生类型多线程数据安全问题。

  • 解决方案:
    • Cache Aside Pattern 人工编码方式:由缓存调用者在更新数据库的同时更新缓存,也称为 双写方案
    • Read/Write Through Pattern:缓存和数据库整合为一个服务,数据库和缓存的问题交由系统本身处理
    • Write Behind Caching Pattern:调用者只操作缓存,其他线程去异步处理数据库,实现最终一致性

经综合考虑,一般采用方案一,采用方案一时,需要考虑的问题

  • 删除缓存还是更新缓存

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

    应该采用删除缓存,如果采用更新缓存,那么每次操作数据库之后,都要进行缓存更新,如果在反复操作数据库的过程中,没有人进行过查询操作,那么可以认为这些更新缓存的操作,只要最后一次是有效的,其他的都是无用功,没什么意义,所有我们可以把缓存进行删除,等待再次查询时,在进行缓存更新

  • 需要保证缓存与数据库的操作的同时成功和失败

    • 单体系统:将缓存与数据库操作放在一个事务
    • 分布式系统:利用TCC等分布式事务方案
  • 先操作缓存还是先操作数据库

    • 先删除缓存,再操作数据库

      image-20230704101541357
      image-20230704101541357
    • 先操作数据库,再删除缓存

      image-20230704103354081
      image-20230704103354081

    应该先操作数据库,在删除缓存,因为我们先删除缓存,在操作数据库,假设两个线程并发访问时,线程1先进入,它先删除了缓存,还没操作数据库呢,线程2进来进行查询,它查询缓存数据并不存在,于是它从数据库中获取数据,并写入缓存,当线程2写入缓存后,线程1才完成数据库的更新操作,那么这个时候,数据库的数据是新数据,缓存的数据还是旧数据,会造成数据不一致问题。

总结:

  • 缓存更新策略的最佳实践方案为:

    • ① 低一致性需求:使用Redis自带的内存淘汰机制;

    • ② 高一致性需求:主动更新,并以超时剔除作为兜底方案

      • 读操作
        • 缓存命中则直接返回
        • 缓存未命中则查询数据库,并写入缓存,设定超时时间
      • 写操作
        • 先写数据库,然后在删除缓存
        • 需要确保数据库与缓存操作的原子性
实现商铺的缓存与数据库双写一致
  • 分析:

    • 根据上面总结的读操作,需要修改根据ID查询商铺信息,

      • 缓存命中则直接返回
      • 缓存未命中,则进行数据库查询,并将数据库查询结果写入缓存,并设置超时时间
      /**
       * redis中缓存商铺信息
       */

      public static final String CACHE_SHOP_KEY = "cache:shop:";
      /**
       * redis中缓存商铺信息的有效时间
       */

      public static final Long CACHE_SHOP_TTL = 30L;
      @Override
      public Shop getShopById(Long id) {
          //组装redis中的key
          String cacheShopKey = CACHE_SHOP_KEY + id;
          //根据ID在redis中查询商铺信息
          String shopString = stringRedisTemplate.opsForValue().get(cacheShopKey);
          //redis中查询到商铺信息
          if (StrUtil.isNotBlank(shopString)){
              Shop shop = BeanUtil.toBean(shopString, Shop.class);
              return shop;
          }
          //根据商铺id查询商铺信息
          Shop shop = this.getById(id);
          //数据库中没查询到该商铺信息
          if (ObjectUtil.isNull(shop)){
              return null;
          }
          //数据库中查询到了该商铺信息,写入缓存,并设置有效时间为30分钟
          stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
          //返回给商铺信息
          return shop;
      }
    • 根据上面总结的写操作,需要编写根据ID更新店铺信息

      • 根据ID更新店铺信息时,先修改数据库,再删除缓存,并确保操作数据库和操作缓存的原子性
      /**
       * 更新商铺信息
       * @param shop 商铺数据
       * @return 无
       */

      @PutMapping
      public Result updateShop(@RequestBody Shop shop) {
          // 写入数据库
          if (ObjectUtil.isNull(shop.getId())){
              return Result.fail("店铺Id不能为空");
          }
          shopService.updateShopById(shop);
          return Result.ok();
      }
      void updateShopById(Shop shop);
      /**
       * 根据id更新商铺信息
       * @param shop
       */

      @Transactional //通过事务,来保证数据库更新和缓存删除的一致性
      @Override
      public void updateShopById(Shop shop) {
          this.updateById(shop);
          stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());
      }

本文由 mdnice 多平台发布

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

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

相关文章

rocketMq消息队列原生api使用以及rocketMq整合springboot

rocketMq消息队列 文章目录 rocketMq消息队列一、RocketMQ原生API使用1、测试环境搭建2、RocketMQ的编程模型3、RocketMQ的消息样例3.1 基本样例3.2 顺序消息3.3 广播消息3.4 延迟消息3.5 批量消息3.6 过滤消息3.7 事务消息3.8 ACL权限控制 二、SpringBoot整合RocketMQ1、快速实…

PLC工程师到C#上位机开发:成功转型的故事

从自动化PLC工程师转变为C#上位机开发工程师&#xff01;这是一个很大的转变&#xff0c;但是您的自动化背景将为您提供宝贵的经验和技能。刚好&#xff0c;我这里有上位机入门&#xff0c;学习线路图&#xff0c;各种项目&#xff0c;需要留个6。 在成功转变的过程中&#xf…

7.3.3 【Linux】磁盘格式化(创建文件系统)

XFS 文件系统 mkfs.xfs 我们常听到的“格式化”其实应该称为“创建文件系统 &#xff08;make filesystem&#xff09;”&#xff0c;使用的是mkfs&#xff0c;创建的是xfs文件系统&#xff0c;使用的是mkfs.xfs这个指令。这个指令这样用&#xff1a; 使用默认的xfs文件系统参…

速通协程,一步到位!

前言 协程的概念最核心的点就是一段程序能够被挂起&#xff0c;稍后在挂起的位置恢复&#xff0c;挂起和恢复是由使用者控制的。 数学模型 在一个线程的视角中&#xff0c;我们的程序是按照顺序来执行的&#xff0c;假设我们使用??????来描述一段程序的所有指令。那么…

elk中logstash的使用

1.前言 logstash是一个相对较重的日志收集器&#xff0c;可以通过多种方式获取到日志数据&#xff0c;如tcp、日志文件、kafka、redis、rabbitmq等方式&#xff0c;还可以使用filter去过滤日志、转换日志为json格式&#xff0c;所以logstash是一个功能强大的日志收集器&#x…

MFC项目添加外部头文件和源文件后编译出现C1010错误

出现这个问题的主要原因是如果使用VC向生成工程的话&#xff0c;默认使用预编译头文件“stdafx.h”&#xff0c;这样做的目的是为了加快编译速度。 如果加入第三方c/cpp文件没有#include “stdafx.h” &#xff0c;就会报此错误。 在<解决方案管理器中>(就是可以看到工程…

NLLloss,KLDivLoss,CrossEntropyLoss三类损失函数比对

前置知识 这三个函数在深度学习模型中十分常见&#xff0c;尤其是在知识蒸馏领域&#xff0c;经常会将这三个函数进行比较 1、Softmax函数 softmax函数通常作为多分类以及归一化函数使用&#xff0c;其公式如下&#xff1a; s o f t m a x ( x ) e x i ∑ i 1 e x i soft…

Drools概述和基本原理

目录 ​编辑 一、Drools是什么&#xff1f; 二、Drools使用场景 三、Drool架构内容 3.1 总体架构 3.2 构成内容说明 3.2.1 Rules 3.2.2 Production memory 3.2.3 Facts 3.2.4 Working memory 3.2.5 Pattern matcher 3.2.6 Agenda 四、为什么要用规则引擎&#xff1f; 4.1 声明…

el-descriptions的使用

el-descriptions的使用 解释&#xff1a; 我们页面有很多无序的列表展示&#xff0c;为了高效得去开发我们得页面&#xff0c;可以借助于这个组件进行适应。图片&#xff1a; 代码&#xff1a; template部分 <el-descriptions class"margin-top" :column"…

IP数据云揭示高风险IP的来源地

在全球网络安全日临近之际&#xff0c;IP数据云揭示高风险IP的来源地。这些高风险IP以其潜在威胁和犯罪活动而闻名&#xff0c;已引起了全球范围内的关注。 根据IP数据云介绍&#xff0c;高风险IP的主要来源地是位于亚洲和东欧的国家其中包括俄罗斯、朝鲜和乌克兰等地。这些地区…

论文阅读:Segment Anything之阅读笔记

目录 引言整体结构介绍论文问答代码仓库中&#xff0c;模型哪部分转换为了ONNX格式&#xff1f;以及如何转的&#xff1f;Mask decoder部分 Transformer decoder block?如何整合image_embedding&#xff0c;image_pe, sparse_prompt_embedding和dense_prompt_embedding的&…

Android Framework基础面试必问习题~

AMS 下面是一些可能会被问到的 Android Framework 中 Activity Manager Service (AMS) 相关的面试题&#xff1a; 1.什么是 AMS&#xff1f; AMS 是 Android framework 中的一个系统进程&#xff0c;它负责管理应用程序生命周期&#xff0c;处理应用程序间的交互和协调不同组…

用Electron将Vue项目打包成桌面版软件

创建Electron项目这里是直接通过官方教程创建的 要检查 Node.js 是否正确安装&#xff0c;请在您的终端输入以下命令&#xff1a; node -vnpm -v这两个命令应输出了 Node.js 和 npm 的版本信息。 创建Electron应用程序 使用脚手架创建 Electron 应用程序遵循与其他 Node.js…

【Squid 代理服务器应用】

目录 一、Squid 代理服务器1、代理的工作机制2、代理服务器的概念及其作用3、Squid 代理的类型 二、安装 Squid 服务1&#xff0e;编译安装 Squid2&#xff0e;修改 Squid 的配置文件3&#xff0e;Squid 的运行控制1、检查配置文件语法是否正确2、启动 Squid&#xff0c;第一次…

在SpringBoot中对es集群的查询操作

在进行查询之前要先给ll索引中插入数据: POST /ll/product/1 {"id":1,"title": "小米手机Mix","category": "手机","brand": "小米","price": 2899.00,"images": "http://ima…

【数据结构】——常见排序算法(演示图+代码+算法分析)

目录 1. 常见排序算法 1.2 稳定性 2. 常见排序算法的实现 2.1 插入排序 2.1.1基本思想 2.1.2代码 2.1.4算法分析 2.2 希尔排序 2.2.1基本思想 2.2.2代码 2.2.3演示图 2.2.4算法分析 2.3 选择排序 2.3.1基本思想 2.3.2代码 2.3.3演示图 2.3.4算法分析 2.4 堆排…

[Visual Studio 报错] error 找不到指定的 SDK“Microsoft

[Visual Studio 2022 报错] error : 找不到指定的 SDK“Microsoft.NET.Sdk.Web” 问题描述&#xff1a; 在新电脑上安装了VS2022&#xff0c;打开现有项目的解决方案后的时候报了这个错&#xff0c;所有projet文件都加载失败,如图所示&#xff1a; 报错分析及解决 打开项目配…

黑客是怎样炼成的?

前言 首先我谈下对黑客&网络安全的认知&#xff0c;其实最重要的是兴趣热爱&#xff0c;不同于网络安全工程师&#xff0c;他们大都是培训机构培训出来的&#xff0c;具备的基本都是防御和白帽子技能&#xff0c;他们绝大多数的人看的是工资&#xff0c;他们是为了就业而学…

MFC扩展库BCGControlBar Pro v33.5新版亮点 - 控件、脚本管理增强

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v33.5已正式发布了&#xff0c;此版本包含了Ribbon&#xff08;功能区&#xff09;自定义…

CSS文本样式

CSS文本样式 1、字体 友情提醒&#xff1a; 字体有没有版权&#xff1f; 省略写法 语法&#xff1a; [ [ <‘font-style’> || || <‘font-weight’> || <‘font-stretch’> ]? <‘font-size’> [ / <‘line-height’> ]? <‘font-fam…