redis缓存单体服务测试本地锁失效问题

news/2024/7/17 7:48:29

测试1:锁释放之后向redis缓存存入数据

在这里插入图片描述

//TODO 产生堆外内存溢出 OutOfDirectMemoryError
    //gulimall.com查询分类
    @Override
    public Map<String, List<CategoryLevel2Vo>> getCatelogJson() {
        /**
         * 问题 :解决办法
         * 1.缓存穿透 高并发情况下查询缓存不存在的数据导致并发查数据库    解决办法:查询数据库结果为null时,在缓存中设置不为null的空值(0,1),并设置过期时间,保证缓存能查到该数据
         * 2.缓存雪崩 高并发情况下查询时,因为key设置相同的过期时间而key集体失效     解决办法:给key设置随机过期时间,防止集体失效 set("categoryJson",s,1,xxx)
         * 3.缓存击穿 高并发情况下同时访问同一个Key   解决办法:加锁
         */

        String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
        //缓存为空就查数据库
        if (StringUtils.isEmpty(categoryJson)){
            System.out.println("缓存未命中,查数据库");
            //查数据库
            Map<String, List<CategoryLevel2Vo>> fromData = getCatelogJsonFromData();
            //存入redis
            String s = JSON.toJSONString(fromData);
            stringRedisTemplate.opsForValue().set("categoryJson",s,1, TimeUnit.DAYS);
            return fromData;
        }
        System.out.println("缓存命中,直接返回");
        //缓存有数据直接返回缓存数据
        Map<String, List<CategoryLevel2Vo>> object = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
        return object;
    }

查数据库逻辑

//Map<一级分类id,二级分类集合>
    public Map<String, List<CategoryLevel2Vo>> getCatelogJsonFromData() {

        synchronized (this) {
            /**
             * 这种加锁,此项目只部署在一台服务器的情况下可以,本地锁锁住一个实例
             * 分布式情况下就不行,此项目部署在多台服务器上吗,每个锁锁住自己的实例只放一个线程进来
             * 如果8太服务器就会有8个线程同时访问,失去了锁的作用
             * 本地锁只能锁住当前进程,不能锁住其他服务
             *
             */
            //TODO 本地锁(当前进程锁)synchronized 、 (JUC)Lock  ,分布式情况下必须使用分布式锁
            //下一个线程拿到锁之后先查缓存,缓存中没有再查数据库,避免频繁查库
            String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
            if (StringUtils.isNotBlank(categoryJson)){
                Map<String, List<CategoryLevel2Vo>> object = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
                return object;
            }
            System.out.println("当前进程锁查询了数据库");
            List<CategoryEntity> categoryGetAll = baseMapper.selectList(null);
            //一级分类
            List<CategoryEntity> category1Level = getParentCid(categoryGetAll,0L);
            Map<String, List<CategoryLevel2Vo>> collect = category1Level.stream().collect(Collectors.toMap(item -> item.getCatId().toString()
                    , l1 -> {
                        //二级分类
                        List<CategoryEntity> category2Level = getParentCid(categoryGetAll,l1.getCatId());
                        List<CategoryLevel2Vo> collect2 = null;
                        if (category2Level != null){
                            collect2 = category2Level.stream().map(l2 -> {
                                //三级分类
                                List<CategoryEntity> category3Level = getParentCid(categoryGetAll,l2.getCatId());
                                List<CategoryLevel2Vo.CategoryLevel3Vo> collect3 = null;
                                if (category3Level != null){
                                    collect3 = category3Level.stream().map(level3 -> {
                                        return new CategoryLevel2Vo.CategoryLevel3Vo(l2.getCatId().toString(),level3.getCatId().toString(),level3.getName());
                                    }).collect(Collectors.toList());
                                }
                                return new CategoryLevel2Vo(l1.getCatId().toString(),collect3,l2.getCatId().toString(),l2.getName());
                            }).collect(Collectors.toList());
                        }
                        return collect2;

                    }));
            return collect;

        }

    }

JMeter性能压测

测试环境

  • 测试之前清空redis缓存
  • 因为是在本地服务器上面运行,单体应用服务器测试进程锁synchronized,实例对象为当前服务(数量:1)
  • 测试线程数:100

预期结果:只查询一次数据库,根据逻辑,如果控制台只打印一次 “当前进程锁查询了数据库” 代表成功

在这里插入图片描述

在这里插入图片描述
测试结果
线程锁测试失败,两次查询数据库,打印了两次 “当前进程锁查询了数据库”

缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
当前进程锁查询了数据库
缓存未命中,查数据库
缓存未命中,查数据库
2023-06-04 00:05:18.208  INFO 6348 --- [io-10001-exec-7] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
2023-06-04 00:05:18.297  INFO 6348 --- [io-10001-exec-7] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
缓存未命中,查数据库
2023-06-04 00:05:18.304 DEBUG 6348 --- [io-10001-exec-7] c.a.g.p.dao.CategoryDao.selectList       : ==>  Preparing: SELECT cat_id,name,parent_cid,cat_level,show_status,sort,icon,product_unit,product_count FROM pms_category WHERE show_status=1 
缓存未命中,查数据库
2023-06-04 00:05:18.316 DEBUG 6348 --- [io-10001-exec-7] c.a.g.p.dao.CategoryDao.selectList       : ==> Parameters: 
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
2023-06-04 00:05:18.344 DEBUG 6348 --- [io-10001-exec-7] c.a.g.p.dao.CategoryDao.selectList       : <==      Total: 1425
当前进程锁查询了数据库
2023-06-04 00:05:18.351 DEBUG 6348 --- [o-10001-exec-42] c.a.g.p.dao.CategoryDao.selectList       : ==>  Preparing: SELECT cat_id,name,parent_cid,cat_level,show_status,sort,icon,product_unit,product_count FROM pms_category WHERE show_status=1 
2023-06-04 00:05:18.351 DEBUG 6348 --- [o-10001-exec-42] c.a.g.p.dao.CategoryDao.selectList       : ==> Parameters: 
缓存未命中,查数据库
2023-06-04 00:05:18.365 DEBUG 6348 --- [o-10001-exec-42] c.a.g.p.dao.CategoryDao.selectList       : <==      Total: 1425
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回

测试2:锁释放之前向redis缓存存入数据

修改代码,在锁释放前存入redis

//TODO 产生堆外内存溢出 OutOfDirectMemoryError
    //gulimall.com查询分类
    @Override
    public Map<String, List<CategoryLevel2Vo>> getCatelogJson() {
        /**
         * 问题 :解决办法
         * 1.缓存穿透 高并发情况下查询缓存不存在的数据导致并发查数据库    解决办法:查询数据库结果为null时,在缓存中设置不为null的空值(0,1),并设置过期时间,保证缓存能查到该数据
         * 2.缓存雪崩 高并发情况下查询时,因为key设置相同的过期时间而key集体失效     解决办法:给key设置随机过期时间,防止集体失效 set("categoryJson",s,1,xxx)
         * 3.缓存击穿 高并发情况下同时访问同一个Key   解决办法:加锁
         */

        String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
        //缓存为空就查数据库
        if (StringUtils.isEmpty(categoryJson)){
            System.out.println("缓存未命中,查数据库");
            //查数据库
            Map<String, List<CategoryLevel2Vo>> fromData = getCatelogJsonFromData();
            return fromData;
        }
        System.out.println("缓存命中,直接返回");
        //缓存有数据直接返回缓存数据
        Map<String, List<CategoryLevel2Vo>> object = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
        return object;
    }

    //Map<一级分类id,二级分类集合>
    public Map<String, List<CategoryLevel2Vo>> getCatelogJsonFromData() {

        synchronized (this) {
            /**
             * 这种加锁,此项目只部署在一台服务器的情况下可以,本地锁锁住一个实例
             * 分布式情况下就不行,此项目部署在多台服务器上吗,每个锁锁住自己的实例只放一个线程进来
             * 如果8太服务器就会有8个线程同时访问,失去了锁的作用
             * 本地锁只能锁住当前进程,不能锁住其他服务
             *
             */
            //TODO 本地锁(当前进程锁)synchronized 、 (JUC)Lock  ,分布式情况下必须使用分布式锁
            //下一个线程拿到锁之后先查缓存,缓存中没有再查数据库,避免频繁查库
            String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
            if (StringUtils.isNotBlank(categoryJson)){
                Map<String, List<CategoryLevel2Vo>> object = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
                return object;
            }
            System.out.println("当前进程锁查询了数据库");
            List<CategoryEntity> categoryGetAll = baseMapper.selectList(null);
            //一级分类
            List<CategoryEntity> category1Level = getParentCid(categoryGetAll,0L);
            Map<String, List<CategoryLevel2Vo>> collect = category1Level.stream().collect(Collectors.toMap(item -> item.getCatId().toString()
                    , l1 -> {
                        //二级分类
                        List<CategoryEntity> category2Level = getParentCid(categoryGetAll,l1.getCatId());
                        List<CategoryLevel2Vo> collect2 = null;
                        if (category2Level != null){
                            collect2 = category2Level.stream().map(l2 -> {
                                //三级分类
                                List<CategoryEntity> category3Level = getParentCid(categoryGetAll,l2.getCatId());
                                List<CategoryLevel2Vo.CategoryLevel3Vo> collect3 = null;
                                if (category3Level != null){
                                    collect3 = category3Level.stream().map(level3 -> {
                                        return new CategoryLevel2Vo.CategoryLevel3Vo(l2.getCatId().toString(),level3.getCatId().toString(),level3.getName());
                                    }).collect(Collectors.toList());
                                }
                                return new CategoryLevel2Vo(l1.getCatId().toString(),collect3,l2.getCatId().toString(),l2.getName());
                            }).collect(Collectors.toList());
                        }
                        return collect2;

                    }));

            /**
             * 为什么将存入redis放在释放锁之前?
             * 线程拿到锁会去查缓存是否有数据,又因为我们向redis存入缓存数据是在释放锁之后
             * 那么释放锁之后,下一个线程查缓存,上一个线程并未存入完成。此时就会出现查询多次数据库的情况,锁失效
             * 故,存缓存数据应在锁释放之前完成
             */
            //存入缓存redis
            String s = JSON.toJSONString(collect);
            stringRedisTemplate.opsForValue().set("categoryJson",s,1, TimeUnit.DAYS);

            return collect;

        }

    }

测试结果
情况redis缓存,再次测试

缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
缓存未命中,查数据库
当前进程锁查询了数据库
2023-06-04 00:15:55.335  INFO 20204 --- [o-10001-exec-84] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-06-04 00:15:55.489  INFO 20204 --- [o-10001-exec-84] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-06-04 00:15:55.495 DEBUG 20204 --- [o-10001-exec-84] c.a.g.p.dao.CategoryDao.selectList       : ==>  Preparing: SELECT cat_id,name,parent_cid,cat_level,show_status,sort,icon,product_unit,product_count FROM pms_category WHERE show_status=1 
2023-06-04 00:15:55.506 DEBUG 20204 --- [o-10001-exec-84] c.a.g.p.dao.CategoryDao.selectList       : ==> Parameters: 
2023-06-04 00:15:55.528 DEBUG 20204 --- [o-10001-exec-84] c.a.g.p.dao.CategoryDao.selectList       : <==      Total: 1425
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回
缓存命中,直接返回

测试成功,只打印了一次“当前进程锁查询了数据库”
在这里插入图片描述
单体服务本地锁测试成功,如果是分布式服务使用单体锁synchronized,那么多少服务实例就会有多少进程,虽然体量不大,但是未达到加锁的目的,为了能更好的解决缓存击穿问题,分布式情况下就不能使用单体进程锁了,必须使用分布式锁
在这里插入图片描述
分布式模拟测试,这里同时启动4个服务,执行线程数100,循环五次
在这里插入图片描述

在这里插入图片描述
测试结果:
只有第一个服务查询了一个数据库,其他服务未查询数据库,这里测试成功的原因可能是并发量不大


http://www.niftyadmin.cn/n/393579.html

相关文章

皮卡丘../../(目录遍历)/敏感信息泄露/PHP反序列化

一.目录遍历 1.概述 在web功能设计中,很多时候我们会要将需要访问的文件定义成变量&#xff0c;从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时&#xff0c;便会将请求的这个文件的值(比如文件名称)传递到后台&#xff0c;后台再执行其对应的文件。 在这个过程中…

mysql密码字段类型

数值 mysql 的数值数据类型可以大致划分为两个类别&#xff0c;一个是整数&#xff0c;另一个是浮点数或小数。 许多不同的子类型对这些类别中的每一个都是可用的&#xff0c;每个子类型支持不同大小的数据&#xff0c;并且 MySQL 允许我们指定数值字段中的值是否有正负之分(U…

衡量距离的公式

内积欧氏距离曼哈顿距离p范数编辑距离汉明距离

数据库四种事务隔离级别的区别以及可能出现的问题

文章目录 1.数据库并发操作带来的主要问题及原因&#xff1a;① 丢失修改② 脏读③ 不可重复读 2.四种事务隔离级别的区别以及可能出现的问题&#xff1a;① Read uncommitted&#xff08;读未提交&#xff09;② Read committed&#xff08;读提交&#xff09;③ Repeatable r…

chatgpt赋能python:Python反转语句:实用技巧提升编程能力

Python反转语句&#xff1a;实用技巧提升编程能力 Python是一门流行的编程语言&#xff0c;许多开发者都会选择Python作为主要的开发语言。其中一个原因是Python的语法简单易学&#xff0c;不仅适合初学者入门&#xff0c;也能被经验丰富的开发者用来构建复杂应用程序。在本文…

阿里云 Windows Server 2022 安装 Docker

阿里云Windows Server 2022 安装 Docker 文章目录 情景尝试正解 安装Docker管理工具安装Docker重启系统配置Docker系统路径配置Docker引擎(也许不用)启动Docker服务 情景 情景&#xff1a;最近一直在搞微服务&#xff0c;团队的服务器是阿里云的 Windows Server 2022&…

xxjob代码执行过程

文章目录 简介xxl-job定时任务的种类xxl-job相关的数据表 调度服务JobScheduleHelper.start().run任务执行列表获取执行任务 执行器执行方法 参考资料 简介 调度中心&#xff1a; 负责管理调度信息&#xff0c;按照调度配置发出调度请求&#xff0c;自身不承担业务代码。调度系…

eBPF 开发实践:使用 eBPF 隐藏进程或文件信息

eBPF&#xff08;扩展的伯克利数据包过滤器&#xff09;是 Linux 内核中的一个强大功能&#xff0c;可以在无需更改内核源代码或重启内核的情况下&#xff0c;运行、加载和更新用户定义的代码。这种功能让 eBPF 在网络和系统性能分析、数据包过滤、安全策略等方面有了广泛的应用…