为什么需要分布式锁?

  • 系统是单机版,那么是在同一个JVM虚拟机内,使用synchronized或者Lock接口,就可以锁住当前线程,保证共享变量的正确性,其使用范围是在同一个进程内。
  • 系统是微服务架构,synchronized和Lock不再起作用,资源类在不同的服务器之间不能共享了,所以需要分布式锁来解决这个问题。
  • 想要实现分布式锁,必须借助外部系统(redis或者Zookeeper)

redisson unlock会删除key redis trylock_Redis

 Redis实现分布锁

        接下来讲述redis是如何一步步实现分布锁的过程

  • v1.0

       利用 setnx 命令 + del key命令 加锁解锁。

redisson unlock会删除key redis trylock_分布式锁_02

       出现了问题

       如果业务抛出异常没有及时释放锁,进程挂了,没有机会释放锁。


  • v2.0

       给锁设置一个过期时间 set key value EX 10 NX(注意命令保证原子性,EX:在key多少秒之后过期,NX:不存在创建)在finally层释放锁。

       出现了问题:

       业务执行的时间超过了锁的过期时间,导致锁被人拿走,然后自己执行到finally把别人锁释放掉

  • v3.0

       加锁时,设置自己的唯一标识(UUID)。释放掉锁前判断(注意命令保证原子性)

SET lock $uuid EX 20 NX

// 判断锁是自己的,才释放
if redis.call("GET",KEYS[1]) == ARGV[1]
then
    return redis.call("DEL",KEYS[1])
else
    return 0
end

     出现问题:

     锁过期时间不好判断

  • v4.0

       Redisson是一个Java语言实现的Redis SDK客户端,Redisson 封装了很多易用的功能 可重入锁、乐观锁、公平锁、读写锁、Redlock

       使用Redisson,它采用了自动续期的方案来避免锁过期,也就是看门狗线程。

redisson unlock会删除key redis trylock_数据库_03

      以上的场景都是锁在单个Redis实例可能产生的问题,Redis集群是AP,在master-salve模式中异步赋值会造成信息丢失,锁丢失。 

     

  • v5.0

       放弃master-salve模式,引入N个节点,官方建议是5个。客户端超过半数的redis实例才算获取到锁。

redisson unlock会删除key redis trylock_redis_04

//CACHE_KEY_REDLOCK为redis 分布式锁的key
RLock lock1 = redissonClient1.getLock(CACHE_KEY_REDLOCK);
RLock lock2 = redissonClient2.getLock(CACHE_KEY_REDLOCK);
RLock lock3 = redissonClient3.getLock(CACHE_KEY_REDLOCK);

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLockBoolean;

try {
    //waitTime 抢锁的等待时间,正常情况下 等3秒
    //leaseTime就是redis key的过期时间,正常情况下等5分钟300秒。
    isLockBoolean = redLock.tryLock(3, 300, TimeUnit.SECONDS);
    log.info("线程{},是否拿到锁:{} ",Thread.currentThread().getName(),isLockBoolean);
    if (isLockBoolean) {
        System.out.println(Thread.currentThread().getName()+"\t"+"---come in biz");
        //业务逻辑,忙10分钟
        try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
    }
} catch (Exception e) {
    log.error("redlock exception ",e);
} finally {
    // 无论如何, 最后都要解锁
    redLock.unlock();
}

学习参考:

1. 水滴与银弹】

 2. 周阳 Redis