Redis分布式锁

介绍

Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。

  • 获取锁
Shell 复制代码
# 添加锁,NX是互斥、EX是设置超时时间
SET lock value NX EX 10
  • 释放锁
Shell 复制代码
# 释放锁,删除即可
DEL key
3ffbbe7d22d54c3ca0c1cfc67835ac20.png

Redis实现分布式锁该如何合理的控制锁的有效时长

可能产生的问题

当线程获取锁成功后,设置了过期时间,如果业务执行的时间太长了,超过了锁的失效时间,锁就会自动释放了,但是业务还没有执行完成,这时有其他线程就可以获取锁成功。此时,就无法保证业务执行的原子性了。

解决方法(Redisson实现的分布式锁-执行流程)

  1. 获取锁:当线程1进入后,获取锁,如果获取成功,直接操作Redis。
  2. WatchDog:如果获取锁成功后,会另外开启一个线程进行监控,WatchDog(看门狗)。
  3. 续期操作:它会不断监听这个持有锁的线程,给这个线程增加锁的持有时间(续期操作)。
  4. 续期默认值:每隔(releaseTime / 3)的时间做一次续期,releaseTime就是锁的过期时间,默认为30秒。默认每隔10秒做一次续期。
  5. 释放锁:业务执行完成后,手动释放锁,并且会通知WatchDog,不需要再监听了。
  6. 竞争情况:在这个过程中,如果有其他线程2尝试加锁,如果加锁成功,流程如上。
  7. 重试机制:在redis中,如果加锁失败,就中断线程2了;在redisson中,设置了一个while循环,不断尝试获取锁,如果在很短时间内,线程1释放了锁,线程2就可以立马获取锁了。
  8. 结果:如果线程1一直没有释放锁,线程2也不会一直循环,redisson设置了阈值,循环一定次数,也会中断。一般情况下线程2不会等很久。

动图示例

aaa6b1f9b38d450d921fa15e8170528b.gif

代码实现

加锁、设置过期时间等操作都是基于lua脚本完成的

java 复制代码
public void redisLock() throws InterruptedException {
        //获取锁 (重入锁),执行锁的名称
        RLock lock = redissonClient.getLock("heimalock");
        try {
            //尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
            // boolean isLock = lock.tryLock(10,30, TimeUnit.SECONDS);
            boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
            //判断是否获取成功
            if (isLock) {
                System.out.println("执行业务...");
            }
        } finally {//释放锁
            lock.unlock();
        }
    }

Redisson实现的分布式锁--可重入

代码示例

java 复制代码
    public void add1() {
        RLock lock = redissonClient.getLock("heimalock");
        boolean isLock = locktryLock();
        //执行业务
        add2();
        //释放锁
        lock.unlock();

    }

    public void add2() {
        RLock lock = redissonClient.getLock("heimalock");
        boolean isLock = lock.tryLock();
        // 执行业务
        // 释放锁
        lock.unlock();
    }

可重入动图示例

利用的hash结构记录的线程id和重入次数

  1. Key就是锁名称,value存储的时当前线程重入的次数
  2. 其中field为线程id,value为线程重入次数
    4c9333054c8347c9b54cc4c0a8caf3c1.gif

具体实现说明

  1. 当线程执行方法1时,获取锁后,记录当前线程id,和入锁次数1.
  2. 当执行方法2时,获取锁后,发现有当前线程id,入锁次数+1,为2.
  3. 当方法2释放锁,入锁次数-1,为1.
  4. 当方法2执行完成,方法1释放锁,入锁次数-1,为0,此时删除Key信息。

redisson实现的分布式锁--主从一致性

问题场景

  1. 如果搭建了主从架构,一个主节点,两个从节点,主节点负责增删改操作,从节点负责读操作,主节点进行主从同步。
  2. 当创建了分布式锁,因为是写数据,找到主节点,进行写操作,正常情况下,主节点要将数据同步给从节点,假如主节点没来得及同步,宕机了。
  3. 依据redis的哨兵机制,两个从节点进行选举,当其中一个被选做主节点
  4. 此时又一个线程进行创建分布式锁,发现新的主节点中没有,此时又创建锁成功,执行业务,导致了脏数据的产生。

问题示例

db9774b3820f4f8485c22eb7ba27427f.gif

解决方法1--红锁

  1. RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n / 2 + 1),避免在一个redis实例上加锁。
  2. n代表节点数量,如果有三台,创建锁的节点至少大于等于2.
    87ba52c4374e49ef8964159e75868201.png
缺点
  1. 实现复杂
  2. 高并发下,性能差
  3. 运维繁琐

解决方法2--zookeeper

  1. redis采用的是AP思想,高可用,可以保证最终一致
  2. 如果业务要求强一致性,可以采用zookeeper的分布式锁,采用的时CP思想,强一致。

目录

Total Likes
2
Total Comments
3