Redis分布式锁实现Redisson 15问

ゝ一纸荒年。 2023-09-28 08:40 32阅读 0赞

068ebb66ad78896a4d70c663c87e4af3.png

在一个分布式系统中,由于涉及到多个实例同时对同一个资源加锁的问题,像传统的synchronized、ReentrantLock等单进程情况加锁的api就不再适用,需要使用分布式锁来保证多服务实例之间加锁的安全性。常见的分布式锁的实现方式有zookeeper和redis等。而由于redis分布式锁相对于比较简单,在实际的项目中,redis分布式锁被用于很多实际的业务场景中。

redis分布式锁的实现中又以Redisson比较出名,所以本文来着重看一下Redisson是如何实现分布式锁的,以及Redisson提供了哪些其它的功能。

一、如何保证加锁的原子性

说到redis的分布式锁,可能第一时间就想到了setNx命令,这个命令保证一个key同时只能有一个线程设置成功,这样就能实现加锁的互斥性。但是Redisson并没有通过setNx命令来实现加锁,而是自己实现了一套完成的加锁的逻辑。

Redisson的加锁使用代码如下,接下来会有几节着重分析一下这段代码逻辑背后实现的原理。

f756263fb3382fca4ece0b2337a8a800.png

先通过RedissonClient,传入锁的名称,拿到一个RLock,然后通过RLock实现加锁和释放锁。

5c0ae1d25be221f408f87d6c4acc3389.png

getLock获得的RLock接口的实现是RedissonLock,所以我们看一下RedissonLock对lock()方法的实现。

49298481ef7b52192c693179c9eef3f2.png

lock方法会调用重载的lock方法,传入的leaseTime为-1,调用到这个lock方法,之后会调用tryAcquire实现加锁的逻辑。

63232eb591f6734e62f2308c404e60c6.png

tryAcquire最后会调到tryAcquireAsync方法,传入了leaseTime和当前加锁线程的id。tryAcquire和tryAcquireAsync的区别就是tryAcquireAsync是异步执行,而tryAcquire是同步等待tryAcquireAsync的结果,也就是异步转同步的过程。

c4bde621b78c12c6493fca1538363ac6.png

tryAcquireAsync方法会根据leaseTime是不是-1来判断使用哪个分支加锁,其实不论走哪个分支,最后都是调用tryLockInnerAsync方法来实现加锁,只不过是参数不同罢了。但是我们这里的leaseTime其实就是-1,所以会走下面的分支,尽管传入到tryAcquireAsync的leaseTime是-1,但是在调用tryLockInnerAsync方法传入的leaseTime参数是internalLockLeaseTime,默认是30s。

tryLockInnerAsync方法。

84b287cd4aed687c5f30dd8637eede6b.png

通过tryLockInnerAsync方法的实现可以看出,最终加锁是通过一段lua脚本来实现加锁的,redis在执行lua脚本的时候是可以保证加锁的原子性的,所以Redisson实现加锁的原子性是依赖lua脚本来实现的。其实对于RedissonLock这个实现来说,最终实现加锁的逻辑都是通过tryLockInnerAsync来实现的。

来一张图总结一下lock方法加锁的调用逻辑。

2749540304844c1ab1b9d60d10c5f24b.png

二、如何通过lua脚本实现加锁

通过上面分析可以看出,redis是通过执行lua脚本来实现加锁,保证加锁的原子性。那么接下来分析一下这段lua脚本干了什么。

b7f3f68ec4c4389a8d72fc0ee75d1555.png

其中这段脚本中的lua脚本中的参数的意思:

  • KEYS[1]:就是锁的名称,对于我们的demo来说,就是myLock
  • ARGV[1]:就是锁的过期时间,不指定的话默认是30s
  • ARGV[2]:代表了加锁的唯一标识,由UUID和线程id组成。一个Redisson客户端一个UUID,UUID代表了一个唯一的客户端。所以由UUID和线程id组成了加锁的唯一标识,可以理解为某个客户端的某个线程加锁。

那么这些参数是怎么传过去的呢,其实是在这里。

e357383926d8ce7a1e6e5972bc13df40.png

  • getName:方法就是获取锁的名称
  • leaseTime:就是传入的锁的过期时间,如果指定超时时间就是指定的时间,没指定默认是30s
  • getLockName:就是获取加锁的客户端线程的唯一标识。

分析一下这段lua的加锁的逻辑。

1)先调用redis的exists命令判断加锁的key存不存在,如果不存在的话,那么就进入if。不存在的意思就是还没有某个客户端的某个线程来加锁,第一次加锁肯定没有人来加锁,于是第一次if条件成立。

2)然后调用redis的hincrby的命令,设置

发表评论

表情:
评论列表 (有 0 条评论,32人围观)

还没有评论,来说两句吧...

相关阅读