Redis分布式锁

原理

Redis 锁主要利用 Redis 的 SETNX 命令。

  • 加锁:SETNX key value。键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。
  • 解锁:DEL key。通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。
  • 锁超时:EXPIRE key timeout,。设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。

伪代码如下:

if (setnx(key, 1) == 1){
     
    expire(key, 30)
    try {
     
        //TODO 业务逻辑
    } finally {
     
        del(key)
    }
}

存在问题

原子性问题

SETNX成功之后,将要设置锁超时时间的时候,服务器挂掉、重启、网络故障等原因,导致EXPIRE命令没有执行,锁变成死锁。
Redis分布式锁_第1张图片
可以通过lua代码来实现原子性问题

EVAL "if (redis.call('setnx',KEYS[1],ARGV[1]) < 1) then return 0; end; redis.call('expire',KEYS[1],tonumber(ARGV[2])); return 1;" 1 key value 100

锁误解除

线程A设置的锁的时间超时释放之后,线程A仍未执行完毕,正在执行线程B的过程中,A解锁了线程B加的锁
Redis分布式锁_第2张图片
解决办法:在value上加上当前线程加锁的标识,解锁的时候,校验标识。

// 加锁
String uuid = UUID.randomUUID().toString().replaceAll("-","");
SET key uuid NX EX 30
// 解锁
if (redis.call('get', KEYS[1]) == ARGV[1])
    then return redis.call('del', KEYS[1])
else return 0
end

超时解锁导致并发

上一个图超时解锁带来的并发问题解决如下:

  • 将过期时间设置足够长,保证代码执行完
  • 增加守护线程,为将要过期但未释放的锁增加有效时间。

不可重入

当线程在持有锁的情况下再次请求加锁,如果一个锁支持一个线程多次加锁,那么这个锁就是可重入的。
Redission 加锁示例:

// 如果 lock_key 不存在
if (redis.call('exists', KEYS[1]) == 0)
then
    // 设置 lock_key 线程标识 1 进行加锁
    redis.call('hset', KEYS[1], ARGV[2], 1);
    // 设置过期时间
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
    end;
// 如果 lock_key 存在且线程标识是当前欲加锁的线程标识
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
    // 自增
    then redis.call('hincrby', KEYS[1], ARGV[2], 1);
    // 重置过期时间
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
    end;
// 如果加锁失败,返回锁剩余时间
return redis.call('pttl', KEYS[1]);

无法等待锁释放

如果客户端需要等待锁释放的话,可以通过

  • 轮询:未获取到锁的话,等待一段时间,继续获取锁,并发量大的时候会影响服务器效率
  • Redis的发布订阅功能:锁获取失败的时候,订阅锁释放消息,重新获取锁。

集群下的主备切换

Redis主从部署模式下,锁在主节点加锁成功,命令还未同步到从节点,这个时候主节点挂了,导致数据不一致问题,从而导致并发执行。

集群下的脑裂问题

当出现脑裂问题的时候,有两个master节点,不同的客户端连接不同的 master 节点时,两个客户端可以同时拥有同一把锁。

Redisson实现Redis分布式锁的底层原理

加锁机制,可重入加锁机制

lua脚本保证这段复杂业务逻辑执行的原子性。

Redis分布式锁_第3张图片

释放锁机制

每次释放锁,次数-1。如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用del命令,从redis里删除这个key。

锁互斥机制

加锁的时候,传入标识这个客户端的ID,通常用UUID来表示

watch dog自动延期机制

客户端一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间。

你可能感兴趣的:(每天一道面试题,Redis)