关于redis:Redisson-分布式锁源码-05公平锁加锁

38次阅读

共计 2342 个字符,预计需要花费 6 分钟才能阅读完成。

前言

默认的加锁逻辑是非偏心的。

在加锁失败时,线程会进入 while 循环,始终尝试取得锁,这时候是多线程进行竞争。就是说谁抢到就是谁的。

Redisson 提供了 偏心锁 机制,应用形式如下:

RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的应用办法
fairLock.lock();

上面一起看下偏心锁是如何实现的?

偏心锁

置信小伙伴们看过后面的文章,曾经驾轻就熟了,间接定位到源码办法:RedissonFairLock#tryLockInnerAsync

好家伙,这一大块代码,我截图也截不完,咱们间接剖析 lua 脚本。

PS:尽管咱不懂 lua,然而这一堆堆的 if else 咱们大略还是能看懂的。

因为 debug 发现 command == RedisCommands.EVAL_LONG,所以间接看上面一部分。

这么长,连呼好几声好家伙!

先来看看参数都有啥?

  1. KEYS[1]:加锁的名字,anyLock
  2. KEYS[2]:加锁期待队列,redisson_lock_queue:{anyLock}
  3. KEYS[3]:期待队列中线程锁工夫的 set 汇合,redisson_lock_timeout:{anyLock},是依照锁的工夫戳寄存到汇合中的;
  4. ARGV[1]:锁超时工夫 30000;
  5. ARGV[2]:UUID:ThreadId 组合 a3da2c83-b084-425c-a70f-5d9a08b37f31:1
  6. ARGV[3]:threadWaitTime 默认 300000;
  7. ARGV[4]:currentTime 以后工夫戳。

加锁队列和汇合是含有大括号的字符串。{XXXX} 是指这个 key 仅应用 XXXX 用来计算 slot 的地位。

Lua 脚本剖析

下面的 lua 脚本是分为几块的,咱们别离从不同的角度看下下面代码的执行。

首次加锁(Thread1)

第一局部,因为是首次加锁,所以期待队列为空,间接 跳出循环。这一部分执行完结。

第二局部:

  1. 当锁不存在,期待队列为空或队首是以后线程,两个条件都满足时,进入外部逻辑;
  2. 从期待队列和超时汇合中删除以后线程,这时候期待队列和超时汇合都是空的,不须要任何操作;
  3. 缩小队列中所有期待线程的超时工夫,也不须要任何操作;
  4. 加锁并设置超时工夫。

执行完这里就 return 了。所以前面几局部就临时不看了。

相当于上面两个命令(整个 lua 脚本都是原子的!):

> hset anyLock a3da2c83-b084-425c-a70f-5d9a08b37f31:1 1
> pexpire anyLock 30000

Thread2 加锁

当 Thread1 加锁实现之后,此时 Thread2 来加锁。

Thread2 能够是本实例其余线程,也能够是其余实例的线程。

第一局部,尽管锁被 Thread1 占用了,然而期待队列是空的,间接跳出循环。

第二局部,锁存在,间接跳过。

第三局部,线程是否持锁,没有持锁,间接跳过。

第四局部,线程是否在期待队列中,Thread2 才来加锁,不在外面,间接跳过。

Thread2 最初会来到这里:

  1. 从线程期待队列 redisson_lock_queue:{anyLock} 中获取最初一个线程;
  2. 因为期待队列是空的,所以间接获取以后锁的剩余时间 ttl anyLock
  3. 组装超时工夫 timeout = ttl + 300000 + 以后工夫戳,这个 300000 是默认 60000*5
  4. 应用 zadd 将 Thread2 放到期待线程有序汇合,而后应用 rpush 将 Thread2 再放到期待队列中。

zadd KEYS[3] timeout ARGV[2]

这里应用 zadd 命令别离搁置的是,redisson_lock_timeout:{anyLock},超时工夫戳(1624612689520),线程(UUID2:Thread2)。

其中超时工夫戳当分数,用来在有序汇合中排序,示意加锁的程序。

Thread3 加锁

Thread1 占有了锁,Thread2 在期待,此时线程 3 来了。

获取 firstThreadId2 此时队列是有线程的是 UUID2:Thread2。

判断 firstThreadId2 的分数(超时工夫戳)是不是小于以后工夫戳:

  1. 小于等于则阐明超时了,移除 firstThreadId2;
  2. 大于,则会进入后续判断。

第二、三、四局部都不满足条件。

Thread3 最初也会来到这里:

  1. 从线程期待队列 redisson_lock_queue:{anyLock} 中获取最初一个线程;
  2. 最初一个线程存在,且不是本人,则 ttl = lastThreadId 超时工夫戳 – 以后工夫戳,就是看最初一个线程还有多久超时;
  3. 组装超时工夫 timeout = ttl + 300000 + 以后工夫戳,这个 300000 是默认 60000*5,在最初一个线程的超时工夫上加上 300000 以及以后工夫戳,就是 Thread3 的超时工夫戳。
  4. 应用 zadd 将 Thread3 放到期待线程有序汇合,而后应用 rpush 将 Thread3 再放到期待队列中。

总结

本文次要总结了偏心锁的加锁逻辑,这波及到比拟多的 Redis 操作,做一下简要总结:

  1. Redis Hash 数据结构:寄存以后锁,Redis Key 就是锁,Hash 的 field 是加锁线程,Hash 的 value 是 重入次数;
  2. Redis List 数据结构:充当线程期待队列,新的期待线程会应用 rpush 命令放在队列左边;
  3. Redis sorted set 有序汇合数据结构:寄存期待线程的程序,分数 score 用来是期待线程的超时工夫戳。

须要了解的就是这里会额定增加一个期待队列,以及有序汇合。

对照着 Java 偏心锁源码浏览,了解起来成果更好。

相干举荐

  • Redisson 分布式锁源码 04:可重入锁开释
  • Redisson 分布式锁源码 03:可重入锁互斥
  • Redisson 分布式锁源码 02:看门狗

正文完
 0