0


谷粒商城-高级篇-Day12-性能优化和缓存

文章目录

性能优化

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、读写数据的时候,加上分布式的读写锁

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

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

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

标签: 缓存 服务器 nginx

本文转载自: https://blog.csdn.net/qq_57907966/article/details/128753202
版权归原作者 白小筠 所有, 如有侵权,请联系我们删除。

“谷粒商城-高级篇-Day12-性能优化和缓存”的评论:

还没有评论