Redis分布式锁-实现原理

33

redis分布式锁

Redis实现分布式锁主要利用Redis的setnx命令(“Set if Not Exists”的缩写),语法SETNX key value

# 添加锁,NX是互斥、EX是设置超时时间
SET lock value NX EX 10
# 释放锁,删除即可
DEL key

SET lock value NXSETNX key value效果是一样的。

①我为什么不可以SETNX key value之后再设置EX超时时间呢?这样就不是一条命令而是两条命令,无法保证原子性。

②不设置EX超时时间会怎么样?会导致死锁。

锁的使用场景

假如业务还没结束,就到了锁超时时间,锁就被释放了,就无法保证业务执行的原子性,从而影响业务数据。该怎么解决呢?

一、根据业务执行时间预估

不好控制时间,卡顿、网络抖动都会影响。

二、给锁续期

Redisson实现的分布式锁

Redisson实现的分布式锁是基于Redis的setnx命令实现的,在此基础进行了优化。

public void redisLock() throws InterruptedException {
    //获取锁(重入锁),执行锁的名称
    RLock Lock = redissonClient.getLock("MyLock");
    //尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
    //boolean isLock = Lock.tryLock(10, 30, TimeUnit.SECONDS);
    //如果不指定锁自动释放时间,redisson会有一个watch dog会对锁续期
    boolean isLock = Lock.tryLock(10, TimeUnit.SECONDS);
    //判断是否获取成功
    if (isLock) {
        try {
            System.out.println("执行业务");
        } finally {
            //释放锁
            Lock.unlock();
        }
    }
}
  1. watch dog 可以给锁续期

  2. 抢不到锁的线程会进行尝试等待。

  3. 所有的redis命令基于LUA脚本完成,可以保证执行的原子性。

加锁,设置过期时间等操作都是基于LUA脚本完成的,它可以保证命令执行的原子性的。

Redisson实现的分布式锁-可重入
public void add1(){
    RLock lock = redissonClient.getLock("MyLock");
    boolean isLock = lock.tryLock();
    //执行业务
    add2();
    //释放锁
    lock.unlock();
}
public void add2(){
    RLock lock = redissonClient.getLock("MyLock");
    boolean isLock = lock.tryLock();
    //执行业务
    //释放锁
    lock.unlock();
}

boolean isLock = lock.tryLock();这里是为了判断获取锁的是不是同一个线程,是同一个线程就可以获得锁。

当调用tryLock方法时,Redisson内部会使用哈希表(hash)来记录锁的相关信息

①重入value就会从1变成2

②add2()方法里面unlock了,不会直接删除锁,而是把value从2变成1

③add1()方法里面unlock了,value值从1变成0了,那就会把锁DEL了。

小结:Redisson实现的分布式锁可以重入,但是要判断锁是不是同一个线程的,不是同一个线程的话是互斥的。

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

假如1主2从,有一个线程要写数据到主节点,那就要获取锁,主节点还没来得及同步数据给从节点就宕机了,哨兵模式会在2从随机选1一个当主。

新线程来了后请求的是新的主节点,因为之前主节点数据没同步过来,所以也可以获取锁成功,这个时候两个线程就获得了同一把锁,这样就会脏数据。

RedLock(红锁)

在超过一半的redis实例上创建锁,(n/2)+1,避免在一个redis实例上加锁。

缺点:实现复杂,性能差,运维复杂