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

news2024/11/17 0:31:08

高并发下缓存失效的问题

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

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

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

解决方案

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.coloradmin.cn/o/1900711.html

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

相关文章

【论文阅读】-- Interactive Horizon Graphs:改进多个时间序列的紧凑可视化

Interactive Horizon Graphs: Improving the Compact Visualization of Multiple Time Series 摘要1 引言2 相关工作2.1 多个时间序列的可视化2.2 缩减折线图 &#xff08;RLC&#xff09;2.3 地平线图 &#xff08;HG&#xff09;2.4 大尺度和小尺度变异数据集2.5 多个时间序列…

IPSS模块怎么安装到VOS服务器的,到底有没有效果,是不是能大幅度提升VOS3000安全性呢

由于VOS的普及性&#xff0c;不得不承认VOS确实是非常优秀的软交换&#xff0c;但是很多客户在使用过程中都会遇到各种安全问题&#xff0c;比如话费被盗用了&#xff0c;历史话单一堆的非法呼叫话单&#xff0c;严重的影响到了话务安全&#xff0c;并不是那点话费的事了&#…

浏览器怎么抓包?Wireshark详细教程奉上!

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

校园气象站:科学教育与环境感知

在现代化的校园里&#xff0c;一座座高耸的教学楼、郁郁葱葱的绿植、充满活力的学生群体共同构成了一幅生机勃勃的画卷。然而&#xff0c;在这幅画卷中&#xff0c;有一个不可或缺的元素——校园气象站&#xff0c;它不仅是学生们学习气象知识的窗口&#xff0c;更是连接科学与…

【技术支持】vscode代码格式化空格数量问题

问题 使用AltShiftF代码格式化时&#xff0c;发现有些文件格式化后缩进为2格个空格&#xff0c;有些文件正常4个空格 刨析 发现vue创建的文件使用的是两个空格&#xff0c;而且换行符表示方式也不一样 LF 是 Unix 和 Unix-like 系统&#xff08;如 Linux 和 macOS&#xff0…

边缘概率密度、条件概率密度、边缘分布函数、联合分布函数关系

目录 二维随机变量及其分布离散型随机变量连续型随机变量边缘分布边缘概率密度举例边缘概率密度 条件概率密度边缘概率密度与条件概率密度的区别边缘概率密度条件概率密度举个具体例子 参考资料 二维随机变量及其分布 离散型随机变量 把所有的概率&#xff0c;都理解成不同质量…

最新CorelDRAW2024设计师的必备神器!

Hey&#xff0c;各位创意小能手和设计爱好者们&#xff0c;今天要跟大家安利一个超级给力的设计软件——CorelDRAW 2024&#xff01;如果你还在用那些老旧的设计工具&#xff0c;那你就OUT啦&#xff01;&#x1f389;&#x1f3a8; CorelDRAW全系列汉化版下载网盘分享链接&am…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第55课-芝麻开门(语音 识别 控制3D纪念馆开门 和 关门)

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第55课-芝麻开门&#xff08;语音识别控制3D纪念馆开门和关门&#xff09; 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtn…

Vue 3 中集成 ECharts(附一些案例)

Vue 3 中集成 ECharts 的完全指南 引言 在现代Web开发中&#xff0c;Vue 3以其卓越的性能和灵活的Composition API赢得了广泛的关注。而ECharts&#xff0c;作为开源的一个使用JavaScript实现的强大可视化库&#xff0c;以其丰富的图表类型和高度可定制性成为了数据可视化的首…

基于Qwen2/Lllama3等大模型,部署团队私有化RAG知识库系统的详细教程(Docker+AnythingLLM)

自 ChatGPT 发布以来&#xff0c;大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff0c;大模型&#xff09;得到了飞速发展&#xff0c;它在处理复杂任务、增强自然语言理解和生成类人文本等方面的能力让人惊叹&#xff0c;几乎各行各业均可从中获益。 然…

Vatee万腾平台:智慧生活的无限可能

在科技日新月异的今天&#xff0c;我们的生活正被各种智能技术悄然改变。从智能家居到智慧城市&#xff0c;从个人健康管理到企业数字化转型&#xff0c;科技的力量正以前所未有的速度渗透到我们生活的每一个角落。而在这场智能革命的浪潮中&#xff0c;Vatee万腾平台以其卓越的…

ctfshow-web入门-文件包含(web87)巧用 php://filter 流绕过死亡函数的三种方法

目录 方法1&#xff1a;php://filter 流的 base64-decode 方法 方法2&#xff1a;通过 rot13 编码实现绕过 方法3&#xff1a;通过 strip_tags 函数去除 XML 标签 除了替换&#xff0c;新增 file_put_contents 函数&#xff0c;将会往 $file 里写入 <?php die(大佬别秀了…

Drools开源业务规则引擎(三)- 事件模型(Event Model)

文章目录 Drools开源业务规则引擎&#xff08;三&#xff09;- 事件模型&#xff08;Event Model&#xff09;1.org.kie.api.event2.RuleRuntimeEventManager3.RuleRuntimeEventListener接口说明示例规则文件规则执行日志输出 4.AgentaEventListener接口说明示例监听器实现类My…

leetcode力扣_双指针问题

141. 环形链表 思路&#xff1a;判断链表中是否有环是经典的算法问题之一。常见的解决方案有多种&#xff0c;其中最经典、有效的一种方法是使用 快慢指针&#xff08;Floyd’s Cycle-Finding Algorithm&#xff09;。 初始化两个指针&#xff1a;一个快指针&#xff08;fast&…

文件加密软件谁好用丨2024文件加密软件TOP10推荐

个人和企业都有隐私保护的需求&#xff0c;文件加密可以确保敏感信息不被未授权的人查看。在数据传输或存储过程中&#xff0c;加密可以防止数据被截获或非法访问。企业需要保护其商业机密&#xff0c;如专利、商业策略和客户信息等&#xff0c;防止竞争对手获取。加密可以保护…

腐蚀服务器如何设置管理员

可以设置服主与管理员 控制台中设置&#xff08;需游戏账号在线&#xff09; 服主 添加&#xff1a;在控制台中输入ownerid空格SteamID 删除&#xff1a;在控制台中输入removeowner空格SteamID 管理员 添加&#xff1a;在控制台中输入moderatorid空格SteamID 删除&#…

分布式事务get global lock fail Xid 获取全局锁失败

问题如下&#xff1a; 解决方法&#xff1a;在发生报错的方法上添加本地事务

C#中委托与事件

一、委托 1.1概念 委托是一种引用类型&#xff0c;它可以用于封装并传递方法作为参数。委托可以理解为是一个指向方法的**“指针”&#xff0c;它允许将方法作为参数传递给其他方法或存储在数据结构中&#xff0c;然后稍后调用这些方法。&#xff08;委托可以看作时函数的容器…

雨量监测站:守护大地的晴雨表

雨量监测站是一种专门用于测量和记录降雨量的设施。它通常由雨量计、数据采集器、传输装置和数据处理系统组成。雨量计负责感应雨滴的接触&#xff0c;通过一定的机制将降雨量转化为电信号或数字信号。数据采集器则负责收集这些信号&#xff0c;并将其传输至数据处理系统进行分…

【nvm管理nodejs版本,切换node指定版本】

nvm管理nodejs版本 nvm管理nodejs版本主要功能使用 nvm nvm管理nodejs版本 nvm&#xff08;Node Version Manager&#xff09;顾名思义node版本管理器&#xff0c;无须去node管网下载很多node安装程序;用于管理多个 Node.js 版本的工具。它允许你在同一台机器上同时安装和管理…