<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redisson 分布式鎖源碼 05:公平鎖加鎖

          共 2721字,需瀏覽 6分鐘

           ·

          2021-07-01 22:47

          前言

          默認(rèn)的加鎖邏輯是非公平的。

          在加鎖失敗時(shí),線程會(huì)進(jìn)入 while 循環(huán),一直嘗試獲得鎖,這時(shí)候是多線程進(jìn)行競(jìng)爭(zhēng)。就是說(shuō)誰(shuí)搶到就是誰(shuí)的。

          Redisson 提供了公平鎖機(jī)制,使用方式如下:

          RLock fairLock = redisson.getFairLock("anyLock");
          // 最常見(jiàn)的使用方法
          fairLock.lock();

          下面一起看下公平鎖是如何實(shí)現(xiàn)的?

          1

          公平鎖

          相信小伙伴們看過(guò)前面的文章,已經(jīng)輕車(chē)熟路了,直接定位到源碼方法:RedissonFairLock#tryLockInnerAsync

          好家伙,這一大塊代碼,我截圖也截不完,咱們直接分析 lua 腳本。

          PS:雖然咱不懂 lua,但是這一堆堆的 if else 咱們大概還是能看懂的。

          因?yàn)?debug 發(fā)現(xiàn) command == RedisCommands.EVAL_LONG,所以直接看下面一部分。

          這么長(zhǎng),連呼好幾聲好家伙!

          先來(lái)看看參數(shù)都有啥?

          1. KEYS[1]:加鎖的名字,anyLock
          2. KEYS[2]:加鎖等待隊(duì)列,redisson_lock_queue:{anyLock}
          3. KEYS[3]:等待隊(duì)列中線程鎖時(shí)間的 set 集合,redisson_lock_timeout:{anyLock},是按照鎖的時(shí)間戳存放到集合中的;
          4. ARGV[1]:鎖超時(shí)時(shí)間 30000;
          5. ARGV[2]:UUID:ThreadId 組合 a3da2c83-b084-425c-a70f-5d9a08b37f31:1
          6. ARGV[3]:threadWaitTime 默認(rèn) 300000;
          7. ARGV[4]:currentTime 當(dāng)前時(shí)間戳。

          加鎖隊(duì)列和集合是含有大括號(hào)的字符串。{XXXX} 是指這個(gè) key 僅使用 XXXX 用來(lái)計(jì)算 slot 的位置。

          2

          Lua 腳本分析

          上面的 lua 腳本是分為幾塊的,咱們分別從不同的角度看下上面代碼的執(zhí)行。

          首次加鎖(Thread1)

          第一部分,因?yàn)槭鞘状渭渔i,所以等待隊(duì)列為空,直接 跳出循環(huán)。這一部分執(zhí)行結(jié)束。

          第二部分:

          1. 當(dāng)鎖不存在,等待隊(duì)列為空或隊(duì)首是當(dāng)前線程,兩個(gè)條件都滿(mǎn)足時(shí),進(jìn)入內(nèi)部邏輯;
          2. 從等待隊(duì)列和超時(shí)集合中刪除當(dāng)前線程,這時(shí)候等待隊(duì)列和超時(shí)集合都是空的,不需要任何操作;
          3. 減少隊(duì)列中所有等待線程的超時(shí)時(shí)間,也不需要任何操作;
          4. 加鎖并設(shè)置超時(shí)時(shí)間。

          執(zhí)行完這里就 return 了。所以后面幾部分就暫時(shí)不看了。

          相當(dāng)于下面兩個(gè)命令(整個(gè) lua 腳本都是原子的!):

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

          Thread2 加鎖

          當(dāng) Thread1 加鎖完成之后,此時(shí) Thread2 來(lái)加鎖。

          Thread2 可以是本實(shí)例其他線程,也可以是其他實(shí)例的線程。

          第一部分,雖然鎖被 Thread1 占用了,但是等待隊(duì)列是空的,直接跳出循環(huán)。

          第二部分,鎖存在,直接跳過(guò)。

          第三部分,線程是否持鎖,沒(méi)有持鎖,直接跳過(guò)。

          第四部分,線程是否在等待隊(duì)列中,Thread2 才來(lái)加鎖,不在里面,直接跳過(guò)。

          Thread2 最后會(huì)來(lái)到這里:

          1. 從線程等待隊(duì)列 redisson_lock_queue:{anyLock} 中獲取最后一個(gè)線程;
          2. 因?yàn)榈却?duì)列是空的,所以直接獲取當(dāng)前鎖的剩余時(shí)間 ttl anyLock
          3. 組裝超時(shí)時(shí)間 timeout = ttl + 300000 + 當(dāng)前時(shí)間戳,這個(gè) 300000 是默認(rèn) 60000*5
          4. 使用 zadd 將 Thread2 放到等待線程有序集合,然后使用 rpush 將 Thread2 再放到等待隊(duì)列中。

          zadd KEYS[3] timeout ARGV[2]

          這里使用 zadd 命令分別放置的是,redisson_lock_timeout:{anyLock},超時(shí)時(shí)間戳(1624612689520),線程(UUID2:Thread2)。

          其中超時(shí)時(shí)間戳當(dāng)分?jǐn)?shù),用來(lái)在有序集合中排序,表示加鎖的順序。

          Thread3 加鎖

          Thread1 占有了鎖,Thread2 在等待,此時(shí)線程 3 來(lái)了。

          獲取 firstThreadId2 此時(shí)隊(duì)列是有線程的是 UUID2:Thread2。

          判斷 firstThreadId2 的分?jǐn)?shù)(超時(shí)時(shí)間戳)是不是小于當(dāng)前時(shí)間戳:

          1. 小于等于則說(shuō)明超時(shí)了,移除 firstThreadId2;
          2. 大于,則會(huì)進(jìn)入后續(xù)判斷。

          第二、三、四部分都不滿(mǎn)足條件。

          Thread3 最后也會(huì)來(lái)到這里:

          1. 從線程等待隊(duì)列 redisson_lock_queue:{anyLock} 中獲取最后一個(gè)線程;
          2. 最后一個(gè)線程存在,且不是自己,則 ttl = lastThreadId 超時(shí)時(shí)間戳 - 當(dāng)前時(shí)間戳,就是看最后一個(gè)線程還有多久超時(shí);
          3. 組裝超時(shí)時(shí)間 timeout = ttl + 300000 + 當(dāng)前時(shí)間戳,這個(gè) 300000 是默認(rèn) 60000*5,在最后一個(gè)線程的超時(shí)時(shí)間上加上 300000 以及當(dāng)前時(shí)間戳,就是 Thread3 的超時(shí)時(shí)間戳。
          4. 使用 zadd 將 Thread3 放到等待線程有序集合,然后使用 rpush 將 Thread3 再放到等待隊(duì)列中。

          3

          總結(jié)

          本文主要總結(jié)了公平鎖的加鎖邏輯,這里涉及到比較多的 Redis 操作,做一下簡(jiǎn)要總結(jié):

          1. Redis Hash 數(shù)據(jù)結(jié)構(gòu):存放當(dāng)前鎖,Redis Key 就是鎖,Hash 的 field 是加鎖線程,Hash 的 value 是 重入次數(shù);
          2. Redis List 數(shù)據(jù)結(jié)構(gòu):充當(dāng)線程等待隊(duì)列,新的等待線程會(huì)使用 rpush 命令放在隊(duì)列右邊;
          3. Redis sorted set 有序集合數(shù)據(jù)結(jié)構(gòu):存放等待線程的順序,分?jǐn)?shù) score 用來(lái)是等待線程的超時(shí)時(shí)間戳。

          需要理解的就是這里會(huì)額外添加一個(gè)等待隊(duì)列,以及有序集合。

          對(duì)照著 Java 公平鎖源碼閱讀,理解起來(lái)效果更好。


          - <End /> -




          歷史文章 | 相關(guān)推薦



          瀏覽 74
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一级国产一级日韩一级 | 日本黄色美女网站 | 久久人人妻人人人人妻性色aV | 九九九在线视频观看 | 欧美一级a |