谷粒商城-高级篇-Day12-性能压测和缓存

news2024/11/26 22:29:37

文章目录

  • 性能优化
    • nginx动静分离
    • 优化三级分类的获取(优化业务)
    • 分布式缓存
      • 整合redis
    • 高并发下的缓存失效问题
      • 缓存穿透
      • 缓存雪崩
      • 缓存击穿
    • 解决这些问题
    • 分布式锁
    • Redisson
      • 可重入锁(Reentrant Lock)
        • 指定过期时间
      • 读写锁
      • 闭锁
      • 信号量
      • 使用Redssion解决
      • 缓存一致性

性能优化

nginx动静分离

将静态文件上传到/mydata/nginx/html/static目录下在这里插入图片描述

修改地址,加上/static/

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

修改nginx的配置

修改/mydata/nginx/conf/conf.d下的gulimall.conf

在这里插入图片描述

重新启动nginx

之前的路径没有修改完

修改JS的路径,根据前端请求资源失败结果修改页面路径

优化三级分类的获取(优化业务)

在CategoryServiceImpl中

将数据库的多次查询变为一次查询

1、先查询所有的分类

2、将查询具体分类封装成一个方法

3、根据传入参数决定查询的是几级分类

 @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        /*
         * 将数据库的多次查询变为一次
         *
         */
        List<CategoryEntity> selectList = baseMapper.selectList(null);



        //获取所有Catelog2Vo
         //1、获取所有一级分类,使用之前的方法getLevel1Categorys
        List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);

        //封装为一个map
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //根据一级分类id查询二级分类
            List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
            List<Catelog2Vo> Catelog2Vos=null;
            if (categoryEntities!=null){
                Catelog2Vos = categoryEntities.stream().map(level2Category -> {
                
                    Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());

                    //查找三级分类
                    List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
                    List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
                    if (categoryEntities1!=null){
                      Catelog3List = categoryEntities1.stream().map(level3Category -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());

                            return catelog3Vo;
                        }).collect(Collectors.toList());
                    }
                    catelog2Vo.setCatalog3List(Catelog3List);

                    return catelog2Vo;
                }).collect(Collectors.toList());

            }

            return Catelog2Vos;
        }));


        return parent_cid;
    }


    private List<CategoryEntity> getParent_cit(List<CategoryEntity> selectList,Long parent_cid){
        List<CategoryEntity> collect = selectList.stream().filter(item -> {
            return item.getParentCid() == parent_cid;
        }).collect(Collectors.toList());
        return collect;
    }

分布式缓存

整合redis

1、引入依赖坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置相关配置

spring:
 redis:
   host: 192.168.205.128
   port: 6379

3、测试

@Autowired
private StringRedisTemplate stringRedisTemplate;
    @Test
    public void testRedis(){
        ValueOperations<String, String> stringStringValueOperations =stringRedisTemplate.opsForValue();
    stringStringValueOperations.set("hello","world");

    //查询
        String s = stringStringValueOperations.get("hello");
        System.out.println("缓存的数据是:"+s);
    }

}

4、改造三级业务,加入缓存

将原来查询数据库封装对象的操作封装为一个方法getCatalogJsonFromDb()

加入缓存逻辑

 @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {

        //1、加入缓存逻辑
        String json = redisTemplate.opsForValue().get("getCatalogJson");
        if (StringUtils.isEmpty(json)){
            //2、缓存中没则查询数据库
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //3、查到的数据再放入缓存中
               //转换为json字符串
            String s = JSON.toJSONString(catalogJsonFromDb);
               //放入缓存
            redisTemplate.opsForValue().set("getCatalogJson",s);
            return catalogJsonFromDb;
        }
        //能从缓存中获取数据
          //将获取的json字符串转换为  Map<String, List<Catelog2Vo>>
        Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });

        return stringListMap;


    }

    //从数据库查询并封装数据
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
        /*
         * 将数据库的多次查询变为一次
         *
         */
        List<CategoryEntity> selectList = baseMapper.selectList(null);



        //获取所有Catelog2Vo
        //1、获取所有一级分类,使用之前的方法getLevel1Categorys
        List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);

        //封装为一个map
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //根据一级分类id查询二级分类
            List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
            List<Catelog2Vo> Catelog2Vos=null;
            if (categoryEntities!=null){
                Catelog2Vos = categoryEntities.stream().map(level2Category -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());

                    //查找三级分类
                    List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
                    List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
                    if (categoryEntities1!=null){
                        Catelog3List = categoryEntities1.stream().map(level3Category -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());

                            return catelog3Vo;
                        }).collect(Collectors.toList());
                    }
                    catelog2Vo.setCatalog3List(Catelog3List);

                    return catelog2Vo;
                }).collect(Collectors.toList());

            }

            return Catelog2Vos;
        }));


        return parent_cid;
    }


    private List<CategoryEntity> getParent_cit(List<CategoryEntity> selectList,Long parent_cid){
        List<CategoryEntity> collect = selectList.stream().filter(item -> {
            return item.getParentCid() == parent_cid;
        }).collect(Collectors.toList());
        return collect;
    }

在这里插入图片描述

进行压力测试:

发现出现错误—产生堆外内存溢出,OutOfDirectMemoryError

解决方法:1、升级lettuce客户端,2、切换使用jedis

这里选择切换使用jedis

在pom文件中,排除lettuce使用jedis即可

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

高并发下的缓存失效问题

缓存穿透

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

风险:利用不存在的数据进行攻击,数据库瞬间压力增大,最终导致崩溃

解决:null结果缓存,并加入短暂过期时间

缓存雪崩

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

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

缓存击穿

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

解决:加锁

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

解决这些问题

1、空结果缓存,解决缓存穿透

2、设置过期时间,解决缓存雪崩

            redisTemplate.opsForValue().set("getCatalogJson",s,1, TimeUnit.DAYS);

3、加锁:解决缓存击穿

给查询数据库的时候,加上本地锁,并且在查询数据库之前,再去判断缓存中有没有数据

    //从数据库查询并封装数据
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
        /*
         * 将数据库的多次查询变为一次
         *
         */

        synchronized (this){

            //再去查询缓存
            String list = redisTemplate.opsForValue().get("getCatalogJson");
            if (!StringUtils.isEmpty(list)){
                //

                Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(list, new TypeReference<Map<String, List<Catelog2Vo>>>() {
                });
                return stringListMap;
            }
            
            
            List<CategoryEntity> selectList = baseMapper.selectList(null);
            
            //获取所有Catelog2Vo
            //1、获取所有一级分类,使用之前的方法getLevel1Categorys
            List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);

            //封装为一个map
            Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //根据一级分类id查询二级分类
                List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
                List<Catelog2Vo> Catelog2Vos=null;
                if (categoryEntities!=null){
                    Catelog2Vos = categoryEntities.stream().map(level2Category -> {
                        Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());

                        //查找三级分类
                        List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
                        List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
                        if (categoryEntities1!=null){
                            Catelog3List = categoryEntities1.stream().map(level3Category -> {
                                Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());

                                return catelog3Vo;
                            }).collect(Collectors.toList());
                        }
                        catelog2Vo.setCatalog3List(Catelog3List);

                        return catelog2Vo;
                    }).collect(Collectors.toList());

                }

                return Catelog2Vos;
            }));


            return parent_cid;
        }

    }

但是结果显示查询了多次数据库

原因:

在这里插入图片描述

就会导致查询的结果还没来得及放入缓存中,就释放了锁

导致下一个进程也会去查数据库

解决办法:
在这里插入图片描述

具体实现:

在查询数据库的方法中的最后

            //查询数据库完成后直接放入缓存
            String s = JSON.toJSONString(parent_cid);

            redisTemplate.opsForValue().set("getCatalogJson",s,1, TimeUnit.DAYS);

            return parent_cid;

将getCatalogJson中,查询数据库完成后放入缓存的操作删除

@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {


        //1、加入缓存逻辑
        String json = redisTemplate.opsForValue().get("getCatalogJson");
        if (StringUtils.isEmpty(json)){


            //2、缓存中没则查询数据库
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //3、查到的数据会直接放入缓存

            return catalogJsonFromDb;
        }
        //能从缓存中获取数据
        //将获取的json字符串转换为  Map<String, List<Catelog2Vo>>
        Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });

        return stringListMap;
    }

完整代码:

//TODO 产生堆外内存溢出,OutOfDirectMemoryError
//1、升级lettuce客户端,2、切换使用jedis
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {

        //1、加入缓存逻辑
        String json = redisTemplate.opsForValue().get("getCatalogJson");
        if (StringUtils.isEmpty(json)){

            //2、缓存中没则查询数据库
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //3、查到的数据会直接放入缓存
            return catalogJsonFromDb;
        }
    System.out.println("缓存命中");
        //能从缓存中获取数据
        //将获取的json字符串转换为  Map<String, List<Catelog2Vo>>
        Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });

        return stringListMap;
    }


//从数据库查询并封装数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
    /*
     * 将数据库的多次查询变为一次
     *
     */

    synchronized (this){

        //再去查询缓存
        String list = redisTemplate.opsForValue().get("getCatalogJson");
        if (!StringUtils.isEmpty(list)){
            //

            Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(list, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return stringListMap;
        }


        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //获取所有Catelog2Vo
        //1、获取所有一级分类,使用之前的方法getLevel1Categorys
        List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);

        //封装为一个map
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //根据一级分类id查询二级分类
            List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
            List<Catelog2Vo> Catelog2Vos=null;
            if (categoryEntities!=null){
                Catelog2Vos = categoryEntities.stream().map(level2Category -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());

                    //查找三级分类
                    List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
                    List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
                    if (categoryEntities1!=null){
                        Catelog3List = categoryEntities1.stream().map(level3Category -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());

                            return catelog3Vo;
                        }).collect(Collectors.toList());
                    }
                    catelog2Vo.setCatalog3List(Catelog3List);

                    return catelog2Vo;
                }).collect(Collectors.toList());

            }

            return Catelog2Vos;
        }));


        //查询数据库完成后直接放入缓存
        String s = JSON.toJSONString(parent_cid);
        redisTemplate.opsForValue().set("getCatalogJson",s,1, TimeUnit.DAYS);

        return parent_cid;
    }

}


private List<CategoryEntity> getParent_cit(List<CategoryEntity> selectList,Long parent_cid){
    List<CategoryEntity> collect = selectList.stream().filter(item -> {
        return item.getParentCid() == parent_cid;
    }).collect(Collectors.toList());
    return collect;
}

本地锁只能锁住当前线程,所以我们需要分布式锁

分布式锁

我们可以同时去同一个地方“占坑”,如果占到,就执行逻辑,否则就必须等待,直到释放锁。“占坑”可以去redis,可以去数据库

将对数据库的查询封装为一个方法便于观看

private Map<String, List<Catelog2Vo>> getDataFormDb() {
    String list = redisTemplate.opsForValue().get("getCatalogJson");
    if (!StringUtils.isEmpty(list)) {
        //

        Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(list, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return stringListMap;
    }


    List<CategoryEntity> selectList = baseMapper.selectList(null);

    //获取所有Catelog2Vo
    //1、获取所有一级分类,使用之前的方法getLevel1Categorys
    List<CategoryEntity> level1Categorys = getParent_cit(selectList, 0L);

    //封装为一个map
    Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
        //根据一级分类id查询二级分类
        List<CategoryEntity> categoryEntities = getParent_cit(selectList, v.getCatId());
        List<Catelog2Vo> Catelog2Vos = null;
        if (categoryEntities != null) {
            Catelog2Vos = categoryEntities.stream().map(level2Category -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString(), null, level2Category.getCatId().toString(), level2Category.getName());

                //查找三级分类
                List<CategoryEntity> categoryEntities1 = getParent_cit(selectList, level2Category.getCatId());
                List<Catelog2Vo.Catelog3Vo> Catelog3List = null;
                if (categoryEntities1 != null) {
                    Catelog3List = categoryEntities1.stream().map(level3Category -> {
                        Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());

                        return catelog3Vo;
                    }).collect(Collectors.toList());
                }
                catelog2Vo.setCatalog3List(Catelog3List);

                return catelog2Vo;
            }).collect(Collectors.toList());

        }

        return Catelog2Vos;
    }));


    //查询数据库完成后直接放入缓存
    String s = JSON.toJSONString(parent_cid);
    redisTemplate.opsForValue().set("getCatalogJson", s, 1, TimeUnit.DAYS);

    return parent_cid;
}

使用分布式锁

  //查询数据库----使用分布式锁

   public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

//1、抢占分布式锁,去redis占坑--设置过期时间
       Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300,TimeUnit.SECONDS);
       if (lock){
           //加锁成功

           //设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
          // redisTemplate.expire("lock",30,TimeUnit.SECONDS);
           //但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
             //执行业务
           Map<String, List<Catelog2Vo>> dataFormDb = getDataFormDb();
           //删除锁
           redisTemplate.delete("lock");
           return dataFormDb;
       }
       else{
           //加锁失败--重试--自旋的方式
           return getCatalogJsonFromDbWithRedisLock();
       }



   }

存在的问题:

如果业务时间很长,锁自己过期了,我们直接删除,有可能把别人正在持有的锁删除了。

解决:占锁的时候,值指定一个UUID

   public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

//1、抢占分布式锁,去redis占坑--设置过期时间
       String uuid = UUID.randomUUID().toString();

       Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
       if (lock){
           //加锁成功
           //设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
          // redisTemplate.expire("lock",30,TimeUnit.SECONDS);
           //但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
             //执行业务
           Map<String, List<Catelog2Vo>> dataFormDb = getDataFormDb();
           //删除锁
           String lockValue= redisTemplate.opsForValue().get("lock");
           if (uuid.equals(lockValue)){
               redisTemplate.delete("lock");
           }
           return dataFormDb;
       }
       else{
           //加锁失败--重试--自旋的方式
           return getCatalogJsonFromDbWithRedisLock();
       }
       
   }

还是有问题:

如果在查询redis确定该uuid还存在,后返回的途中,uuid被自动删除了,然后执行删除操作,就会删除别人的锁

解决办法:

删锁必须也是原子操作

lua脚本解锁

if redis.call('get',KEY[1]==ARGV[1] 
then return redis.call('del',KEYS[1]))
else return 0 end
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

 //1、抢占分布式锁,去redis占坑--设置过期时间
        String uuid = UUID.randomUUID().toString();

        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
        if (lock){
            //加锁成功
            System.out.println("获取分布式锁成功");
            //设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
           // redisTemplate.expire("lock",30,TimeUnit.SECONDS);
            //但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
              //执行业务
            Map<String, List<Catelog2Vo>> dataFormDb;
        try {
            dataFormDb = getDataFormDb();
        }finally {
            String script="if redis.call('get',KEY[1]==ARGV[1] then return redis.call('del',KEYS[1])) else return 0 end";

            //删除锁
            Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock", uuid));

        }
//            //删除锁
//            String lockValue= redisTemplate.opsForValue().get("lock");
//            if (uuid.equals(lockValue)){
//                redisTemplate.delete("lock");
//            }
            return dataFormDb;
        }
        else{
            System.out.println("获取分布式锁失败,等待重试");
            //加锁失败--重试--自旋的方式
            try {
                Thread.sleep(200);
            } catch (Exception e) {

            }
            return getCatalogJsonFromDbWithRedisLock();
        }

    }

Redisson

整合redisson作为分布式锁等功能框架

1、引入redisson

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>

2、配置redisson

程序化配置方法

@Configuration
public class MyRedissonConfig {


    //RedissonClient
    @Bean(destroyMethod = "shutdown")
    public  RedissonClient reddis() throws IOException{
        //1、创建配置
        Config config=new Config();
        config.useSingleServer().setAddress("redis://192.168.205.128:6379");
        //2、根据Config,创建出RedissonClient实例
        return Redisson.create(config);
    }
}

测试是否成功

@Test
public  void testReddison(){
    System.out.println(redissonClient);
}

可重入锁(Reentrant Lock)

演示:

 @ResponseBody
    @GetMapping("/hello")
    public String hello(){
        //1 、获取一把锁,只要锁的名字相同,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
        //2、加锁
        lock.lock();//阻塞式等待,默认加的锁都是30s时间
        
        try{
            System.out.println("加锁成功,执行业务、、、、"+Thread.currentThread().getId());
            Thread.sleep(3000);
        }catch (Exception e){

        }finally{
            //3、释放锁
            System.out.println("释放锁"+Thread.currentThread().getId());
            lock.unlock();
        }

        return "hello";
    }

}

访问这个hello

http://localhost:10001/hello

redis中会出现my-lock锁

在这里插入图片描述

1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务的时间长,锁自动过期被删除掉

2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认也会在30s后自动删除

指定过期时间

lock.lock(10, TimeUnit.SECONDS);//设置自动解锁时间为10秒

此时,自动解锁时间大于业务运行时间

则出现异常,在锁时间到了以后不会自动续期

如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时时间就是我们指定的时间

如果我们未传递锁的超时时间,就使用30*1000【LockWatchdogTimeOut看门狗的默认时间】;只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期

读写锁

保证一定能读到最新数据

在修改期间,写锁是一个排他锁,读锁是一个共享锁

写锁没释放读就必须等待

代码演示:

@ResponseBody
@GetMapping("/write")
public String writeValue() {
    String s = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.writeLock();//获取写锁
    try {
        //写数据加写锁
        s= UUID.randomUUID().toString();
        Thread.sleep(3000);
        redisTemplate.opsForValue().set("writeValue", s);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }finally {
        rLock.unlock();
    }
    return s;
}
@ResponseBody
@GetMapping("/read")
public String readValue() {
    String s="";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.readLock();
    try {
        //读数据加读锁

        Thread.sleep(3000);
        s = redisTemplate.opsForValue().get("writeValue");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }finally {
        rLock.unlock();

        
    }
    return s;
}

演示读:从redis中获取writeValue的值

在这里插入图片描述

演示写:写入一个UUID生成的随机数

在写的过程中是读不了的,只有在完成后才能获得最新数据

总结:

读+读:就相当于无锁,并发读,只会在redis中记录好所有当前的读锁。他们都会加锁成功

写+读:等待写锁释放

写+写:阻塞

读+写:有读锁,写需要等待读锁释放

只要有写的存在都必须要等待

闭锁

代码演示

@ResponseBody
@GetMapping("/lockDoor")
public String lockDoor() throws InterruptedException {
    RCountDownLatch door=redisson.getCountDownLatch("door");
    door.trySetCount(5);//给一个闭锁的计数,完成5个就释放闭锁
    door.await();//等待闭锁完成
    return "放假了";
}

@ResponseBody
@GetMapping("/gogo/{id}")
public String gogo(@PathVariable("id") Long id){
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.countDown();
    return id+"班的人都走了";
}

先访问http://localhost:10001/lockDoor页面处于加载状态

然后访问http://localhost:10001/gogo/1,页面显示1班的人走了,一直访问从1到5直到5个班的人都走了,http://localhost:10001/lockDoor页面显示放假了

在这里插入图片描述

信号量

模拟车库停车

代码演示:

@ResponseBody
@GetMapping("/park")
public String park() throws InterruptedException {
    RSemaphore park = redisson.getSemaphore("park");
    park.acquire();
    return "停车成功";
}
@ResponseBody
@GetMapping("/go")
public String go() throws InterruptedException {
    RSemaphore park = redisson.getSemaphore("park");
    park.release();
    return "出库成功";
}

给rediss设置一个park

在这里插入图片描述

访问http://localhost:10001/park

每访问一次,信号量就会减1

在这里插入图片描述

在这里插入图片描述

访问http://localhost:10001/go信号量就会加1

其中acquire和tryAcquire的区别

//        park.acquire();//阻塞式等待
        boolean b = park.tryAcquire();//尝试获取一个信号量,如果尝试失败则返回false,不会阻塞

使用Redssion解决

public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {

    RLock lock = redisson.getLock("CatelogJson-lock");
    lock.lock();
    //设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
        // redisTemplate.expire("lock",30,TimeUnit.SECONDS);
        //但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
        //执行业务
        Map<String, List<Catelog2Vo>> dataFormDb;
        try {
            dataFormDb = getDataFormDb();
        }finally {
       lock.unlock();
        }

        return dataFormDb;
    }

缓存一致性

双写模式:当数据库的数据更新后,缓存数据也要修改

失效模式:当数据库的数据更新后,将缓存的数据删除

解决办法:

1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新

2、读写数据的时候,加上分布式的读写锁

我们能放入缓存的数据本就不应该是实时性,一致性要求超高的,所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可

我们不应该过度设计,增加系统的复杂性

遇到实时性,一致性要求高的数据,就应该查数据库,即使慢点

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

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

相关文章

Python实现一个简易的CLI翻译程序

Python实现一个简易的CLI翻译程序Python百度翻译API实现一个简易的CLI翻译程序获取百度翻译API编写一个简单的Python程序Python百度翻译API实现一个简易的CLI翻译程序 之前翻译用的linux上的golddict,每次翻译都很慢。。。 所以想写一个简单快速的翻译命令行翻译软件 获取百度…

Allegro如何自动高亮不等长的网络操作指导

Allegro如何自动高亮不等长的网络操作指导 在做PCB设计的时候,时常需要要做等长,Allegro可以自动高亮一组内不等长的网络,可以直观的看到哪些网络长度是不满足的,类似下图 绿色的是通过的,红色是长度不足的,粉色是超长的 具体操作如下 选择Route-Timing Vision出现optio…

Springboot359的医院病历管理系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 2 第3章 系统分析 3 3.1 需求分析 3 3.2 系统可行性分析 4 3.2.1技术可行性&#xff1a;技术背景 4 3.2.2经济…

Ubiquiti MAC Address Changer 3.0 Crack

Ubiquiti MAC Address Changer&#xff0c;目前mac address changer的版本有很多&#xff0c;本次发布的是V3版本&#xff0c;这是一款功能非常强大的修改网卡mac地址软件&#xff0c;基本上所有的网卡MAC地址都支持修改&#xff0c;包括虚拟机和TeamViewer软件都是支持的。 Ea…

5、基本数据类型

目录 一、整数类型 二、浮点类型 三、字符类型 四、布尔类型 一、整数类型 整数类型用来存储整数数值&#xff0c;即没有小数部分的数值。可以是正数&#xff0c;也可以是负数。整 型数据在Java程序中有3种表示形式&#xff0c;分别为十进制、八进制和十六进制。 1.十进…

2.4.4 数值类型的转换

文章目录1.运算时的自转2.运算时的强转3.强转时的精度丢失问题1.运算时的自转 不同数字类型之间的大小关系如下&#xff1a;double > float > long > int > char, short,byte 自转&#xff1a;小类型的数据可以直接赋值给大类型的变量&#xff1b; byte short c…

Linux(五)创建一个miniShell

前情提要&#xff1a;掌握进程控制中的进程创建、进程终止、进程等待、进程替换。可以参考下方博文 LInux&#xff08;四&#xff09;进程控制&#xff08;创建、终止、等待、替换&#xff09; 了解strtok函数的使用 正文&#xff1a; 目录 Shell是什么&#xff1f; 如何…

蓝桥杯之二分与前缀和

蓝桥杯之二分二分板子&#xff1f;第一次和最后一次出现的位置机器人跳跃问题四平方和分巧克力&#xff1f;典型二分找大的&#xff08;从右往左找&#xff09;二分upper_bound(a1,an1,x)-a&#xff1f;递增三元组前缀和取余&#xff1f;K倍区间二维前缀和&#xff1f;激光炸弹…

17种编程语言实现排序算法-合并排序

开源地址 https://gitee.com/lblbc/simple-works/tree/master/sort/ 覆盖语言&#xff1a;C、C、C#、Java、Kotlin、Dart、Go、JavaScript(JS)、TypeScript(TS)、ArkTS、swift、PHP。 覆盖平台&#xff1a;安卓(Java、Kotlin)、iOS(SwiftUI)、Flutter(Dart)、Window桌面(C#)、…

分享139个ASP源码,总有一款适合您

ASP源码 分享139个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 139个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1Vk4U4EXVCWZWPMWf9ax2dw?pwdif23 提取码&#x…

【C++】类和对象(上)---什么是类?

目录1.面向过程和面向对象初步认识2.类的引入2.1使用struct定义类3.类的定义3.1类的两种定义方式&#xff1a;3.2成员变量命名规则的建议3.3成员函数与成员变量定义的位置建议4.类的访问限定符及封装4.1访问限定符4.2封装5.类的作用域6.类的实例化7.类对象模型7.1如何计算类对象…

springboot静态资源目录访问,及自定义静态资源路径,index页面的访问

springboot静态资源目录访问&#xff0c;及自定义静态资源路径&#xff0c;index页面的访问静态资源目录的访问位置静态资源访问测试自定义静态资源路径和静态资源请求映射web首页的访问自定义静态资源请求映射影响index.html首页的访问的**解决方案**&#xff1a;1.取消自定义…

【JUC系列】CountDownLatch实现原理

简单示例 public class Main {private static final int NUM 3;public static void main(String[] args) throws InterruptedException {CountDownLatch latch new CountDownLatch(NUM);for (int i 0; i < NUM; i) {new Thread(() -> {try {Thread.sleep(2000);Syste…

梯度之上:Hessian 矩阵

原文链接&#xff1a;原文 文章目录梯度之上&#xff1a;Hessian 矩阵梯度、雅克比矩阵海森矩阵海森矩阵应用梯度之上&#xff1a;Hessian 矩阵 本文讨论研究梯度下降法的一个有力的数学工具&#xff1a;海森矩阵。在讨论海森矩阵之前&#xff0c;需要首先了解梯度和雅克比矩阵…

基础知识一览3

这里写目录标题1.Servlet1.1 入门1.2 什么是Servlet1.3 Servlet的作用1.4 Servlet生命周期1.5 Servler的体系结构1.6 Servler的两种配置方式2.Filter2.1 Filter拦截路径配置2.2 过滤器链2.2 入门2.3 过滤器链2.4 过滤器生命周期3.Listener3.1 监听器分类3.1.1 一类监听器4.Serv…

ESP32设备驱动-GA1A12S202光线传感器驱动

GA1A12S202光线传感器驱动 1、GA1A2S202介绍 GA1A1S202 对数刻度模拟光传感器使用起来非常简单,只需添加电源,然后监控模拟输出。大多数光传感器对光强度具有线性响应,这意味着它们对低光水平非常不敏感,然后在高光水平下达到最大值。另一方面,该传感器具有对数响应,这…

第九届蓝桥杯省赛 C++ B组 - 乘积最大

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;蓝桥杯题解集合 &#x1f4dd;原题地址&#xff1a;乘积最大 &#x1f4e3;专栏定位&#xff1a;为想参加蓝桥杯的小伙伴整理常考算法题解&#xff0c;祝大家…

Thread的join()方法的作用

文章目录官方文档对join()的解释&#xff1a;结合实例解释官方文档对join()的解释&#xff1a; Thread.join() method javadocs&#xff08;点击跳转&#xff09; join() Waits for this thread to die. 线程类的 join()方法将等待子线程完成&#xff0c;然后继续当前线程。j…

【Python】常见的时间操作(时间区间、时间相加减、指定年月天数等。。。

前言 记录在Python中使用的时间操作&#xff0c;方便以后查找。 在使用Python中&#xff0c;常会遇到关于时间的操作。 虽说每次都能借助搜索引擎找到解决的方法&#xff0c;但好记性不如烂笔头&#xff0c;遂有此文。 暂时记录了笔者所使用过的Python关于时间的操作&#xff0…

人世正道沧桑,她亦奋力向前 --读《李清照传》有感

有一次陪小孩晨读&#xff0c;朗诵诗歌&#xff0c;读到了李清照的《夏日绝句》&#xff1a;“生当作人杰&#xff0c;死亦为鬼雄。至今思项羽&#xff0c;不肯过江东”。这首诗是1129年&#xff0c;李清照在芜湖的乌江县触景生情&#xff0c;有感而作的。这首诗写得大气磅礴&a…