redis和redisson分布式锁实现

  1. 分布式锁的基本原理
    多个服务同时去一个地方“占坑”,如果占到就执行逻辑,否则就继续等待,直到释放锁.“占坑”可以去redis,也可以去数据库,或任何所有服务都能访问的地方.等待可以使用自旋的方式.

  2. 分布式锁的优化过程
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    锁的自动续期先设置一个较大的过期时间.

  3. 使用redis实现分布式锁

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson(){
          
          
        // redis缓存存在的三个问题:
        // 缓存穿透: 数据库中不存在数据多次请求,每次都请求数据库。 解决办法:将null值放入缓存,并设置一个过期时间
        // 缓存雪崩: 多个缓存设置过期时间相同,同时失效,导致数据库同时处理大量请求。 解决办法: 过期时间加上一个随机值
        // 缓存击穿: 在缓存过期时间刚到期时有大量请求,导致数据库同时处理大量请求。 解决办法:加锁,只让一个请求请求数据库,从数据库中获取数据后放入缓存,其他请求获取锁后先从缓存获取
        // 从redis中查询
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        // 如果redis中没有,则从数据库中查询
        if(StringUtils.isEmpty(catalogJSON)){
          
          
            return getCatalogJsonFromDbWithRedisLock();
        }
        // 转为指定的类型
        return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){
          
          });
    }
    
    /**
     * redis分布式锁实现
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock(){
          
          
        // 1.占分布式锁,去redis占坑
        String uuid = UUID.randomUUID().toString();
        // 当lock锁不存在的时候加锁,锁的值为UUID值,同时设置过期时间
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if(lock){
          
          
            System.out.println("获取分布式锁成功");
            // 2. 加锁成功,执行业务
            Map<String, List<Catelog2Vo>> dataFromDb;
            try{
          
          
                dataFromDb = getDataFromDb();
            }finally {
          
          
                // 3. 通过lua脚本实现比较和删除锁同时执行(比较uuid和lock锁对应的value是否相等,如果相等则删除锁,返回1,否则返回0)
                String script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        }else{
          
          
            try{
          
          
                Thread.sleep(200);
            }catch (Exception e){
          
          
    
            }
            System.out.println("获取分布式锁失败。。等待重试");
            // 自旋
            return getCatalogJsonFromDbWithRedisLock();
        }
    }
    
    
    /**
     * 从数据库中获取数据
     * @return
     */
    public Map<String, List<Catelog2Vo>> getDataFromDb(){
          
          
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if(!StringUtils.isEmpty(catalogJSON)){
          
          
            return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){
          
          });
        }
        // 将数据的多次查询变成一次
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        // 1. 查出所有的一级分类
        List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
        // 2. 封装数据
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
          
          
            // 根据一级分类,查到二级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            // 封装上面的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
          
          
                catelog2Vos = categoryEntities.stream().map(l2 -> {
          
          
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    // 1. 根据二级分类查到三级分类封装成vo
                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                    if(level3Catelog != null){
          
          
                        List<Catelog2Vo.Catelog3Vo> catelog3Vos = level3Catelog.stream().map(l3->{
          
          
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(catelog3Vos);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        // 从数据库中查到数据后,将数据转为json放入缓存中(因为json具有跨语言、跨平台兼容的特性)
        String s = JSON.toJSONString(parent_cid);
        redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
        return parent_cid;
    }
    
  4. 使用redisson实现分布式锁

    /**
     * 使用redisson实现分布式锁
     * getCatalogJson方法和getDataFromDb方法同上
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
          
          
        RLock lock = redisson.getLock("CatalogJson-lock");
        lock.lock();
        Map<String, List<Catelog2Vo>> dataFromDb;
        try{
          
          
            dataFromDb = getDataFromDb();
        }finally {
          
          
            lock.unlock();
        }
        return dataFromDb;
    }
    
  5. redisson一般锁实现

    @ResponseBody
    @GetMapping("/hello")
    public String hello(){
          
          
        // 1. 获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
        // 2. 加锁. 堵塞式等待,默认加的锁都是30s时间
        // 锁的自动续期:如果业务时间较长,运行期间会自动给锁续上新的30s,不用担心业务时间长导致锁自动过期被删掉
        // 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
        lock.lock();
    
        // 10s自动解锁,自动解锁时间一定要大于业务执行时间(因为在在业务执行期间,锁过期时间到了之后,不会自动续期)
        // 1)如果指定了锁的超时时间,就会发送给redis lua执行脚本,进行占锁,默认超时时间就是指定的时间
        // 2)如果没有指定锁的超时时间,就是用LockWatchdogTimeout(看门狗的默认超时时间:30*1000,即30s)
        //    只要占锁成功,就会启动一个定时任务(重新给锁设置过期时间,新的过期时间就是看门狗的默认时间),每隔看门狗的默认时间/3(即默认10s),就会自动续期
        // lock.lock(10, TimeUnit.SECONDS);
        try{
          
          
            System.out.println("加锁成功,执行业务。。。" + Thread.currentThread().getId());
            Thread.sleep(30000);
        }catch (Exception e){
          
          
    
        }finally {
          
          
            // 3. 解锁 即使解锁代码没有运行,默认30s后也会自动释放锁(前提是业务代码执行完毕,否则会自动延时)
            System.out.println("释放锁。。。" + Thread.currentThread().getId());
            lock.unlock();
        }
        return "hello";
    }
    
  6. redisson读写锁实现

    // 读写锁能够保证一定能读到最新数据。修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁
    // 写锁没释放,读和写都必须等待;读锁没释放,都可以读,写必须等待
    @GetMapping("/write")
    @ResponseBody
    public String writeValue(){
          
          
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = lock.writeLock();
        try{
          
          
            // 改数据加写锁,读数据加读锁
            rLock.lock();
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue", s);
        }catch (InterruptedException e){
          
          
            e.printStackTrace();
        }finally {
          
          
            rLock.unlock();
        }
        return s;
    }
    
    @GetMapping("/read")
    @ResponseBody
    public String readValue(){
          
          
        String s = "";
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        // 加读锁
        RLock rLock = lock.readLock();
        rLock.lock();
        try{
          
          
            s = redisTemplate.opsForValue().get("writeValue");
        }catch (Exception e){
          
          
            e.printStackTrace();
        }finally {
          
          
            rLock.unlock();
        }
        return s;
    }
    
  7. redisson闭锁实现

    /**
     * 闭锁(确保某些活动直到其他活动都完成后才继续执行)之锁门测试用例
     * 5个班的人都走了才可以锁大门
     * @return
     */
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
          
          
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();
        return "放假了";
    }
    
    @GetMapping("/gogogo/{id}")
    public String gogogo(@PathVariable("id") Long id){
          
          
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();;
        return id + "班的人都走了";
    }
    
  8. redisson信号量实现

    /**
     * 信号量之停车用例测试
     */
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
          
          
        RSemaphore park = redisson.getSemaphore("park");
        park.acquire();  // 获取一个信号,占一个车位
        return "ok";
    }
    
    @GetMapping("/go")
    public String go(){
          
          
        RSemaphore park = redisson.getSemaphore("park");
        park.release();  // 释放一个车位
        return "ok";
    }
    

猜你喜欢

转载自blog.csdn.net/qq_26496077/article/details/114885872