Redis(十五)——实现分布式锁

1、基于set命令的分布式锁

加锁:使用setnx进行加锁,该指令返回1时,加锁成功。

解锁:使用del释放,以便其他线程可以继续获取锁

存在问题:A线程获取锁后还没释放就挂了,死锁。

解决方案:设置超时时间

2、加锁时带上超时时间

set <key> <value> nx ex <expireTime>

存在问题:线程A设置锁的过期时间是30s,但是线程A还没有执行完,锁过期自动释放,线程B拿到了。等A执行完想删除锁时,会删掉线程B加的锁。

解决方案:锁的value设置为自己的的线程ID,删除前做判断。但是查询、判断、删除锁不是原子性的,对非原子性的问题,可以使用Lua脚本确保操作的原子性

3、锁续期

虽然删除前做判断,但依旧不太合理,线程A使用过程中就应该是锁没有过期的。

解决方案:获得锁的线程开一个守护线程,自动为线程A持有的锁续期(「看门狗」机制,Redisson)。有2种情况释放锁。

  • 线程A结束,结束守护线程,显示释放锁或者等锁自动过期。
  • 线程A所在的服务器挂了,守护线程跟着没了,锁自动过期。

4、如果单个redis挂了咋办?RedLock

如果是主从模式,主从服务器数据并不是强一致性,主节点挂了之后从节点上位,新的主节点并不存在旧主节点的锁,会导致多个线程都可以操作一把锁,则该锁不安全。

解决方案:每次对半数以上的Redis节点加锁,这样基本保证不会同时挂掉。假设有5个redis节点,则加锁步骤如下

  • 拿到当前时间,并且设置一个过期时间TTL。
  • 依次对5个redis节点加锁,此时需要设置一个超时时间,避免请求失败等异常。超时时间内还无法获取锁,则放弃,找下一个。
  • 获取半数以上(3个以上)的锁,才算成功。
  • 获取锁的有效时间 = TTL - 获取锁的时间。
  • 如果获取锁失败,则需要一一解锁。(如果是加锁成功但是返回消息丢失,也需要解锁)
  • 失败重试:获取锁失败,应该在随机时间后重试,同时要有重试次数的限制。
posted @ 2023-02-12 16:37  守林鸟  阅读(135)  评论(0编辑  收藏  举报