redisTemplate分布式锁演变、redission分布式锁实现!

基本原理

我们可以同时去一个地方“占坑”,如果占到,就执行逻辑。否则就必须等待,直到释放锁。“占坑”可以去redis,可以去数据库,可以去任何大家都能访问的地方。等待可以自旋的方式。

阶段一

  1.  
    public Map<StringList<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
  2.  
            //阶段一
  3.  
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock""111");
  4.  
            //获取到锁,执行业务
  5.  
            if (lock) {
  6.  
                Map<StringList<Catalog2Vo>> categoriesDb = getCategoryMap();
  7.  
                //删除锁,如果在此之前报错或宕机会造成死锁
  8.  
                stringRedisTemplate.delete("lock");
  9.  
                return categoriesDb;
  10.  
            }else {
  11.  
                //没获取到锁,等待100ms重试
  12.  
                try {
  13.  
                    Thread.sleep(100);
  14.  
                } catch (InterruptedException e) {
  15.  
                    e.printStackTrace();
  16.  
                }
  17.  
                return getCatalogJsonDbWithRedisLock();
  18.  
            }
  19.  
        }
  20.  
     
  21.  
    public Map<StringList<Catalog2Vo>> getCategoryMap() {
  22.  
            ValueOperations<StringString> ops = stringRedisTemplate.opsForValue();
  23.  
            String catalogJson = ops.get("catalogJson");
  24.  
            if (StringUtils.isEmpty(catalogJson)) {
  25.  
                System.out.println("缓存不命中,准备查询数据库。。。");
  26.  
                Map<StringList<Catalog2Vo>> categoriesDb= getCategoriesDb();
  27.  
                String toJSONString = JSON.toJSONString(categoriesDb);
  28.  
                ops.set("catalogJson", toJSONString);
  29.  
                return categoriesDb;
  30.  
            }
  31.  
            System.out.println("缓存命中。。。。");
  32.  
            Map<StringList<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<StringList<Catalog2Vo>>>() {});
  33.  
            return listMap;
  34.  
        }
  35.  
     

问题: setnx占好了位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁

解决: 设置锁的自动过期,即使没有删除,会自动删除

阶段二

  1.  
     public Map<StringList<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
  2.  
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock""111");
  3.  
            if (lock) {
  4.  
                //设置过期时间
  5.  
                stringRedisTemplate.expire("lock"30, TimeUnit.SECONDS);
  6.  
                Map<StringList<Catalog2Vo>> categoriesDb = getCategoryMap();
  7.  
                stringRedisTemplate.delete("lock");
  8.  
                return categoriesDb;
  9.  
            }else {
  10.  
                try {
  11.  
                    Thread.sleep(100);
  12.  
                } catch (InterruptedException e) {
  13.  
                    e.printStackTrace();
  14.  
                }
  15.  
                return getCatalogJsonDbWithRedisLock();
  16.  
            }
  17.  
        }
  18.  
     

问题: setnx设置好,正要去设置过期时间,宕机。又死锁了。

解决: 设置过期时间和占位必须是原子的。redis支持使用setnx ex命令

阶段三

  1.  
    public Map<StringList<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
  2.  
        //加锁的同时设置过期时间,二者是原子性操作
  3.  
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock""1111",5, TimeUnit.SECONDS);
  4.  
        if (lock) {
  5.  
            Map<StringList<Catalog2Vo>> categoriesDb = getCategoryMap();
  6.  
            //模拟超长的业务执行时间
  7.  
            try {
  8.  
                Thread.sleep(6000);
  9.  
            } catch (InterruptedException e) {
  10.  
                e.printStackTrace();
  11.  
            }
  12.  
            stringRedisTemplate.delete("lock");
  13.  
            return categoriesDb;
  14.  
        }else {
  15.  
            try {
  16.  
                Thread.sleep(100);
  17.  
            } catch (InterruptedException e) {
  18.  
                e.printStackTrace();
  19.  
            }
  20.  
            return getCatalogJsonDbWithRedisLock();
  21.  
        }
  22.  
    }
  23.  
     

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

解决: 占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除。

阶段四

  1.  
    public Map<StringList<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
  2.  
            String uuid = UUID.randomUUID().toString();
  3.  
            ValueOperations<StringString> ops = stringRedisTemplate.opsForValue();
  4.  
          //为当前锁设置唯一的uuid,只有当uuid相同时才会进行删除锁的操作
  5.  
            Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
  6.  
            if (lock) {
  7.  
                Map<StringList<Catalog2Vo>> categoriesDb = getCategoryMap();
  8.  
                String lockValue = ops.get("lock");
  9.  
                if (lockValue.equals(uuid)) {
  10.  
                    try {
  11.  
                        Thread.sleep(6000);
  12.  
                    } catch (InterruptedException e) {
  13.  
                        e.printStackTrace();
  14.  
                    }
  15.  
                    stringRedisTemplate.delete("lock");
  16.  
                }
  17.  
                return categoriesDb;
  18.  
            }else {
  19.  
                try {
  20.  
                    Thread.sleep(100);
  21.  
                } catch (InterruptedException e) {
  22.  
                    e.printStackTrace();
  23.  
                }
  24.  
                return getCatalogJsonDbWithRedisLock();
  25.  
            }
  26.  
        }
  27.  
     

问题: 如果正好判断是当前值,正要删除锁的时候,锁已经过期,别人已经设置到了新的值。那么我们删除的是别人的锁

解决: 删除锁必须保证原子性。使用redis+Lua脚本完成

阶段五-最终形态

  1.  
     public Map<StringList<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
  2.  
            String uuid = UUID.randomUUID().toString();
  3.  
            ValueOperations<StringString> ops = stringRedisTemplate.opsForValue();
  4.  
            Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
  5.  
            if (lock) {
  6.  
                Map<StringList<Catalog2Vo>> categoriesDb = getCategoryMap();
  7.  
                String lockValue = ops.get("lock");
  8.  
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
  9.  
                        "    return redis.call(\"del\",KEYS[1])\n" +
  10.  
                        "else\n" +
  11.  
                        "    return 0\n" +
  12.  
                        "end";
  13.  
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);
  14.  
                return categoriesDb;
  15.  
            }else {
  16.  
                try {
  17.  
                    Thread.sleep(100);
  18.  
                } catch (InterruptedException e) {
  19.  
                    e.printStackTrace();
  20.  
                }
  21.  
                return getCatalogJsonDbWithRedisLock();
  22.  
            }
  23.  
        }
  24.  
     

保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。更难的事情,锁的自动续期

Redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。

其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)

Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

更多请参考官方文档:

https://github.com/redisson/redisson/wiki

posted @ 2022-05-23 14:53  姚春辉  阅读(589)  评论(0)    收藏  举报