谷粒商城----通过缓存和分布式锁获取数据。

高并发下缓存失效的问题

高并发下缓存失效的问题--缓存穿透

指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义

导致缓存穿透的就是红色的这条线

解决方案

null结果缓存,并且设置一个短暂的过期时间。

高并发下缓存失效的问题--缓存雪崩

缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。(大面积Key同时失效)

解决方案

原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低就很难引发集体失效的事件。

高并发下缓存失效的问题--缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

解决方案

加锁 大量并发只让一个去查,其他人等待,查到以后释放锁其他人获取到锁,先查缓存,就会有数据,不用去db

谷粒商城中的锁

接口层

/**
 * @description: 从服务器中获取一段JSON数据
 * @param: []
 * @return: java.util.Map<java.lang.String,java.util.List<com.atguigu.gulimail.product.vo.Catelog2Vo>>
 */
@ResponseBody
@GetMapping("/index/catalog.json")
public Map<String, List<Catelog2Vo>> getCatelogJson() {
    Map<String, List<Catelog2Vo>> catelogJson = categoryService.getCatelogJson();
    return catelogJson;
}

业务层

getCatelogJson

首先去缓存中获取JSON

String catalogJSONFromCache = redisTemplate.opsForValue().get("catalogJSON");

  • 有 ,通过阿里的fast-json,把redis中的字符串解析成对象,由于该对象是个复杂类型的,所以这里使用TypeReference并用泛型,指定上类型。

  • 没有,调用getCatelogJsonFromDataWithRedisLock,使用缓存锁的情况下,从数据库中获取数据。

@Override
public Map<String, List<Catelog2Vo>> (){
​
    /*a
    * 1. 空结果缓存
    * 2. 设置随机值的过期时间
    * 3. 缓存击穿
    * */
​
    //1.加入缓存的功能
    String catalogJSONFromCache = redisTemplate.opsForValue().get("catalogJSON");
​
    if(StringUtils.isEmpty(catalogJSONFromCache)){
        //2.缓存中没有,查询数据库
        return getCatelogJsonFromDataWithRedisLock();
    }
    //转为指定的对象,使用阿里的fastJSON,如果是复杂的数据类型,需要指定一个TypeReference,这里直接使用匿名内部类,进行转换
    return JSON.parseObject(catalogJSONFromCache,new TypeReference<Map<String,List<Catelog2Vo>>>(){});
}

getCatelogJsonFromDataWithRedisLock

进入getCatelogJsonFromDataWithRedisLock方法,在这里使用的分布式缓存锁,主要应对缓存失效的问题:

1. 缓存雪崩
1. 缓存穿透
1. 缓存击穿
String token = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",token,300,TimeUnit.SECONDS);

这里的锁,必须是要有过期时间的

万一拿到锁的服务,去操作数据库,出现异常,或者机器突然崩掉,就会导致锁无法释放,就会出现死锁的 状况。

set lock "haha" nx

nx:只有在没有这个k-v的情况下,才可以设置成功,否则就是失败的。

问题:

  1. 万一拿到锁的服务,去操作数据库,出现异常,或者机器突然崩掉,就会导致锁无法释放,就会出现死锁的状况。

    1. 解决方法,设置个自动过期时间(一定要设置成原子操作,ex)

    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300,TimeUnit.SECONDS);

这里每个人的锁,也就是key,对应的value都是一个不相同的UUID。

如果业务超时,会导致多人都拿到锁,而且前面的线程会删掉后面线程的锁,导致业务异常。

解决方法,设置的Value都不尽相同,可以是UUID随机值,之后在比对值后,才进行删除(一定要设置成 原子操作)。

锁的抢占

redisTemplate.opsForValue().setIfAbsent("lock",token,300,TimeUnit.SECONDS);

这些事朝Redis中放入一个key,如果不存在这个key,设置上,并返回true,如果有这个key,则直接返回false。

可以通过这个结果来判断抢占锁的成功与否。

  • 如果锁抢占失败:

    • 先让线程睡200ms。

    • 然后再次调用这个方法:getCatelogJsonFromDataWithRedisLock,抢占资源。

  • 如果锁抢占成功:

    • 执行正在从数据库或得数据的方法:data = getDataFromDB();

    • 原子操作释放锁。

      • 为什么这里要使用脚本操作?保证原子性。为什么要原子性?

        如果不是原子操作:

 

public Map<String, List<Catelog2Vo>> getCatelogJsonFromDataWithRedisLock() {
​
        //抢占锁 ,并且设置过期时间(30s之后删除)
        //每一个线程的锁都是不一样的。
        String token = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",token,300,TimeUnit.SECONDS);
        //返回的结果
        if(lock){
            System.err.println("获取分布式锁成功");
            Map<String, List<Catelog2Vo>> data = null;
            try{
               //加锁成功,执行业务
               data = getDataFromDB();
           }finally {
               String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
               Integer result = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class),
                       Collections.singletonList("lock"),
                       token
               );
           }
            //业务执行成功,解锁
//            redisTemplate.delete("lock");
​
            //删锁的就需要进行值比对
//            String lockValue = redisTemplate.opsForValue().get("lock");
//            if(lockValue.equals(token)){
//                //删除自己的锁
//                redisTemplate.delete("lock");
//            }
            //返回结果
            return data;
        }else{
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
​
            }
            System.err.println("获取分布式锁不成功...等待重试");
            //加锁失败,重试
            return getCatelogJsonFromDataWithRedisLock();
        }
    }

getDataFromDB

String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");

这里为什么要进行第二次的查看缓存数据?

getCatelogJson方法中,就进行了一次缓存的读取,为什么这里还需要一次呢?可以想象一个场景:当缓存中没有数据时,用大量并发线程都访问这个方法获取JSON数据,第一次的查缓存肯定是null,所以所有的线程都进入了getCatelogJsonFromDataWithRedisLock方法,排队获得锁,第一个获得锁的线程,读取数据,把内容放到缓存之后,就是剩下的线程获得锁,进入getDataFromDB这个方法,如果不再次查缓存的话,剩下的 线程就还是从数据库中获取数据,因为第一层缓存查询,没有过滤掉这些线程(用词可能不合适),这下就变成排队查数据库了,还是出现了缓存失效的问题。

private Map<String, List<Catelog2Vo>> getDataFromDB() {
    //拿到锁之后,第一步就应该是去缓存中看一下,是否已经有了内容,如果有了,就不用去操作数据库了。
    //如果不进行一次,这个锁就失去意义了,就变成排队查数据库了
    String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
    if(!StringUtils.isEmpty(catalogJSON)){
        //缓存不为空,就返回数据
        return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
    }
    System.err.println("====================================Info-Message================================");
    List<CategoryEntity> selectList = baseMapper.selectList(null);
    //1.查出所有1级分类
    List<CategoryEntity> level1 = getParent_cid(selectList, 0L);
    //2.封装数据
    Map<String, List<Catelog2Vo>> parent_cid = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //1.查出1级分类中所有2级分类
                List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                //2.封装上面的结果
                List<Catelog2Vo> catelog2Vos = null;
                if (categoryEntities != null) {
                    catelog2Vos = categoryEntities.stream().map(l2 -> {
                        Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                        //查询当前2级分类的3级分类
                        List<CategoryEntity> level3 = getParent_cid(selectList, l2.getCatId());
                        if (level3 != null) {
                            List<Catelog2Vo.Catelog3Vo> collect = level3.stream().map(l3 -> {
                                //封装指定格式
                                Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                return catelog3Vo;
                            }).collect(Collectors.toList());
                            catelog2Vo.setCatalog3List(collect);
                        }
                        return catelog2Vo;
                    }).collect(Collectors.toList());
                }
                return catelog2Vos;
            }
    ));

整个流程

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

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

相关文章

详解「一本通 5.1 练习 1」括号配对(区间DP经典题)

一.题目 二.思路 题目的大意是说:给你一个只由[ ] ( )构成的字符串&#xff0c;请问需要增加多少个字符才能使其变为一个合法的括号序列。 因为添加若干字符使其达到匹配的目的等价于将不匹配的字符去除使得字符串达到匹配的目的 所以这题只需计算出已匹配完成的括号数,再…

深度学习与CV入门

文章目录 前言历史 前言 历史 tensorflow可以安装Tensorboard第三方库用于展示效果 TensorFlow工作流程&#xff1a;p6-4:20 使用tf.data加载数据。使用tf.data实例化读取训练数据和测试数据模型的建立与调试:使用动态图模式Eager Execution和著名的神经网络高层API框架Ker…

mongoDB教程(五):命名规范

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

拍桌子、甩脸子、抡棒子没用,带出一流战斗力团队用好3招就够了

拍桌子、甩脸子、抡棒子没用&#xff0c;带出一流战斗力团队用好3招就够了 第一招&#xff1a;及时激励 在现实中&#xff0c;绝大部分管理者管理手段缺乏&#xff0c;只知道用钱进行激励。 而真正的高手不仅会满足员工物质上的需求&#xff0c;更注重员工心理上的满足。 他…

cs231n作业1——Softmax

参考文章&#xff1a;cs231n assignment1——softmax Softmax softmax其实和SVM差别不大&#xff0c;两者损失函数不同&#xff0c;softmax就是把各个类的得分转化成了概率。 损失函数&#xff1a; def softmax_loss_naive(W, X, y, reg):loss 0.0dW np.zeros_like(W)num_…

知识社区在线提问小程序模板源码

蓝色的知识问答&#xff0c;问答交流&#xff0c;知识社区&#xff0c;在线提问手机app小程序网页模板。包含&#xff1a;社区主页、提问、我的、绑定手机&#xff0c;实名认证等。 知识社区在线提问小程序模板源码

**kwargs 字典解包传参的方式

字典解包传参 在Python中&#xff0c;****kwargs**是一种通过字典解包 (dictionary unpacking) 的方式进行参数传递的方式。它将一个字典的键值对解包并传递给函数的命名参数。 示例代码 kwargs实参: {name: "jordan", age: 18, score: [80, 85, 85]} get_info形…

U盘非安全退出后的格式化危机与高效恢复策略

在数字化时代&#xff0c;U盘作为数据存储与传输的重要工具&#xff0c;其数据安全备受关注。然而&#xff0c;一个常见的操作失误——U盘没有安全退出便直接拔出&#xff0c;随后再插入时却遭遇“需要格式化”的提示&#xff0c;这不仅让用户措手不及&#xff0c;更可能意味着…

windows内置的hyper-v虚拟机的屏幕分辨率很低,怎么办?

# windows内置的hyper-v虚拟机的屏幕分辨率很低&#xff0c;怎么办&#xff1f; 只能这么大了&#xff0c;全屏也只是把字体拉伸而已。 不得不说&#xff0c;这个hyper-v做的很烂。 直接复制粘贴也做不到。 但有一个办法可以破解。 远程桌面。 我们可以在外面的windows系统&…

科普文:构建可扩展的微服务架构设计方案

前言 微服务架构是一种新兴的软件架构风格&#xff0c;它将单个应用程序拆分成多个小的服务&#xff0c;每个服务都运行在自己的进程中&#xff0c;这些服务通过网络进行通信。这种架构的优势在于它可以提高应用程序的可扩展性、可维护性和可靠性。 在传统的应用程序架构中&…

minist数据集分类模型的训练

minist数据集训练 训练方法&#xff1a;利用pytorch来实现minist数据集的分类模型训练 训练模型如下图所示 模型代码&#xff1a; import torch from torch import nn from torch.nn import Flattenclass Net(nn.Module):def __init__(self):super().__init__()self.module …

grid布局下的展开/收缩过渡效果【vue/已验证可正常运行】

代码来自GPT4o&#xff1a;国内官方直连GPT4o <template><div class"container"><button class"butns" click"toggleShowMore">{{ showAll ? 收回 : 显示更多 }}</button><transition-group name"slide-fade&…

KDP数据分析实战:从0到1完成数据实时采集处理到可视化

智领云自主研发的开源轻量级Kubernetes数据平台&#xff0c;即Kubernetes Data Platform (简称KDP)&#xff0c;能够为用户提供在Kubernetes上的一站式云原生数据集成与开发平台。在最新的v1.1.0版本中&#xff0c;用户可借助 KDP 平台上开箱即用的 Airflow、AirByte、Flink、K…

14-35 剑和诗人9 - 普及 Agentic RAG

好吧&#xff0c;让我们直接进入正题——了解 Agentic RAG&#xff08;检索增强生成&#xff09;方法以及它如何彻底改变我们处理信息的方式。系好安全带&#xff0c;因为这将变得疯狂&#xff01; Agentic RAG 的核心在于为 RAG 框架注入智能和自主性。这就像对常规 RAG 系统…

阶段三:项目开发---搭建项目前后端系统基础架构:任务10:SpringBoot框架的原理和使用

任务描述 1、熟悉SpringBoot框架的原理及使用 2、使用IDEA创建基于SpringBoot、MyBatis、MySQL的Java项目 3、当前任务请在client节点上进行 任务指导 1、SpringBoot框架的选择和原理 2、MyBatis-Plus的选择和原理 3、使用IDEA创建基于SpringBootMyBatis-PlusMySQL的Jav…

PCIe驱动开发(1)— 开发环境搭建

PCIe驱动开发&#xff08;1&#xff09;— 开发环境搭建 一、前言 二、Ubuntu安装 参考: VMware下Ubuntu18.04虚拟机的安装 三、QEMU安装 参考文章&#xff1a;QEMU搭建X86_64 Ubuntu虚拟系统环境 四、安装Ubuntu 下载地址&#xff1a;https://old-releases.ubuntu.com…

QWidget窗口抗锯齿圆角的一个实现方案(支持子控件)2

QWidget窗口抗锯齿圆角的一个实现方案&#xff08;支持子控件&#xff09;2 本方案使用了QGraphicsEffect&#xff0c;由于QGraphicsEffect对一些控件会有渲染问题&#xff0c;比如列表、表格等&#xff0c;所以暂时仅作为研究&#xff0c;优先其他方案 在之前的文章中&#…

k8s_集群搭建_在主节点中加入node节点_k8s集群自恢复能力演示_token过期重新生成令牌---分布式云原生部署架构搭建016

然后安装好了master节点以后,我们再来看如何把node节点加入进来,可以看到 只需要执行,命令行中提示的命令就可以了 比如上面的 Your Kubernetes control-plane has initialized successfully!To start using your cluster, you need to run the following as a regular user:…

人脸识别课堂签到系统【PyQt5实现】

人脸识别签到系统 1、运用场景 课堂签到,上班打卡,进出门身份验证。 2、功能类别 人脸录入,打卡签到,声音提醒,打卡信息导出,打包成exe可执行文件 3、技术栈 python3.8,sqlite3,opencv,face_recognition,PyQt5,csv 4、流程图 1、导入库 2、编写UI界面 3、打…

商家店铺电商小程序模板源码

橙色通用的商家入驻&#xff0c;商户商家&#xff0c;商家店铺&#xff0c;购物商城&#xff0c;商家购物平台app小程序网页模板。包含&#xff1a;商家主页、优先商家、商品详情、购物车、结算订单、个人中心、优惠券、会员卡、地址管理等功能页面。 商家店铺电商小程序模板源…