Redis如何实现分布式锁(二)

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

前言

上一章讲解单机环境Redis如何实现分布式锁,那么针对与主从环境分布式锁将如何实现?可以采用Ression来实现主从环境下分布式锁,本文将对Redisson如何实现分布式锁进行详细讲解。

简介

Redisson是Redis服务器上的分布式可伸缩Java数据结构,并且提供了可重入锁、乐观锁、公平锁、读写锁、红锁的实现。

实现原理

关于Redssion实现分布式锁的原理如下图:

图片.png

说明:

  • 1.客户端线程尝试获取锁,如果获取锁失败,则说明有其他的线程持有锁,当前线程进行自旋处理,尝试获取锁,如果获取锁成功,则执行lua脚本进行加锁。

  • 2.加锁成功后,会开启一个看门狗线程每隔10s判断下锁是否还被当前线程持有,如果持有就会延长锁的过期时间,锁的默认初始过期时间是30s,10s后看门狗判断锁如果还被持有,就执行lua脚本再把锁的过期时间调至30s,这就是锁的续期。

  • 3.程序运行结束后,执行lua脚本释放锁。

如何获取锁

Redisson加锁是通过的执行lua的脚本来实现加锁,具体的脚本内容如下:

if (redis.call('exists', KEYS[1]) == 0) then " +
   "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
   "redis.call('pexpire', KEYS[1], ARGV[1]); " +
   "return nil; " +
   "end; " +
"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]);"

执行流程

图片.png

说明:

  • 1.如果锁不存在,则进行hincrby操作,并设置过期时间。
  • 2.如果锁存在且key也存在,则进行hincrby操作(可重入锁),并以毫秒为单位重新设置过期时间。
  • 3.如果已经存在锁,但不是当前客户端ID,则说明有其他线程获取到了锁(当前线程需要等待),需要执行如下命令返回锁的过期时间。
  return redis.call('pttl', KEYS[1]);

返回数字,该数字代表锁所剩余的时间。

锁自动续期

单机分布式锁存在问题,当执行复杂的业务逻辑时,执行时间过长,导致锁已经释放了,业务逻辑尚未执行完成,而在Redisson提供了一个续期机制,只要客户端一旦加锁成功,就会启动一个Watch Dog线程,Watch Dog机制就是后台起一个定时任务线程,获取锁成功之后,会将持有锁的线程放入到RedissonLock.EXPIRATION_RENEWAL_MAP里面,然后每隔 10 秒检查一下,如果客户端还持有锁 key(判断客户端是否还持有key,如果存在就会延长key的时间),那么就会不断的延长锁key的生存时间。

如何释放锁

Redisson的释放锁也是通过执行lua脚本释放锁

 // 判断锁 key 是否存在
        "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
            "return nil;" +
        "end; " +
        // 将该客户端对应的锁的 hash 结构的 value 值递减为 0 后再进行删除
        //然后再向通道名为 redisson_lock__channel publish 一条 UNLOCK_MESSAGE 信息
        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
        "if (counter > 0) then " +
            "redis.call('pexpire', KEYS[1], ARGV[2]); " +
            "return 0; " +
        "else " +
            "redis.call('del', KEYS[1]); " +
            "redis.call('publish', KEYS[2], ARGV[1]); " +
            "return 1; "+
        "end; " +
        "return nil;",

说明:

  • 1.如果当前线程并没有持有锁,则返回nil。
  • 2.如果当前线程持有锁,则对value值进行减1操作,等到对于的value值。 如果value>0,以毫秒为单位返回剩下的过期时间。 如何value<=0,则对key进行删除操作,返回1。
  • 3.执行redis-pub指令广播释放锁的消息
  • 4.取消Watch Dog机制,将RedissonLock.EXPIRATION_RENEWAL_MAP里面的线程id删除,并且取消定时任务线程。

哨兵集群环境如何加锁

针对哨兵集群环境,还是会存在锁丢失场景,具体流程如下:

图片.png

说明:

  • 1.客户端在主库上执行SET命令加锁成功。
  • 2.此时主库发生宕机,SET命令还未同步到从库
  • 3.从库通过哨兵升为新的主库,而加锁却在旧的主库上,从而能导致新的主库未进行加锁,锁丢失了。

针对上述问题,Redis的作者提出一种解决方案叫做Redlock(红锁).Redlock(红锁)是为了解决主从架构中当出现主从切换导致多个客户端持有同一个锁而提出的一种算法。关于RedLock的实现原理和实现大家可以参考Redis的官方网站,本文不进行阐述说明。

优缺点

优点

  • 1.Redisson通过Watch Dog 机制很好的解决了锁的续期问题
  • 2.通过Redisson实现分布式可重入锁,对比原生的SET mylock userId NX PX milliseconds + lua脚本实现的效果更好,虽然基本原理都一样,但是屏蔽了内部的执行细节。
  • 3.Redisson在等待申请锁的实现上进行相关优化,减少了无效的锁申请,提升了资源的利用率。

缺点

集群模式,主从切换时可能会导致多个客户端同时加锁。

总结

本文对于Ression实现分布式锁进行了详细的讲解,如有疑问请随时反馈。

猜你喜欢

转载自juejin.im/post/7127113652787740709