文章目录
- 性能优化
- 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、读写数据的时候,加上分布式的读写锁
我们能放入缓存的数据本就不应该是实时性,一致性要求超高的,所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可
我们不应该过度设计,增加系统的复杂性
遇到实时性,一致性要求高的数据,就应该查数据库,即使慢点



















