<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>

          redis分布式鎖和2個(gè)房客的故事你聽過嗎

          共 2799字,需瀏覽 6分鐘

           ·

          2021-01-26 12:57


          為什么需要分布式鎖這里就不贅述了。常見的分布式鎖實(shí)現(xiàn)方案有Redis、Zookeeper,數(shù)據(jù)庫(kù)。

          設(shè)計(jì)一個(gè)分布式鎖,至少應(yīng)該保證以下3個(gè)方面:

          1. 安全: 獨(dú)享(相互排斥)。在任意一個(gè)時(shí)刻,只有一個(gè)客戶端持有鎖。
          2. 無死鎖:即便是天塌下來,也要鎖能釋放。
          3. 容錯(cuò)。只要大部分Redis節(jié)點(diǎn)都活著,客戶端就可以獲取和釋放鎖。

          redis部署方案一般有這3種。

          1. 單機(jī)模式

          2. master-slave + sentinel

          3. redis cluster模式

          我們看這3種如何實(shí)現(xiàn)分布式鎖。

          • 單機(jī)模式:正常情況下,單機(jī)模式?jīng)]什么大問題,就怕萬(wàn)一redis掛了就完蛋了。一般我們不會(huì)采用單機(jī)版,這里之所以提到它,是因?yàn)閱螜C(jī)版的鎖是其他方案的基礎(chǔ)。redis鎖的命令是:
          SET?resource_name?my_value?NX?PX?ms?
          //NX是指如果key不存在就就返回true,key存在返回false,PX可以指定過期時(shí)間

          如果簡(jiǎn)單的setnx,有些場(chǎng)景下會(huì)有問題。

          《倆個(gè)房客的故事》 long long a ago,有A、B倆個(gè)房客前往同一家酒店,又都看上了同一間房,但是一間房同時(shí)只能容納一個(gè)人,經(jīng)過一番舌槍唇戰(zhàn),A搶到了第一次。A預(yù)計(jì)自己半個(gè)小時(shí)能完事,于是就開了半個(gè)小時(shí)的鐘點(diǎn)房。可是A這次發(fā)揮超常,30分鐘還沒完事,但是由于30分鐘時(shí)間已經(jīng)到了,房間鎖自動(dòng)打開(房間空置狀態(tài)),可以接下一位客人。這時(shí)B來了,進(jìn)入房間,鎖上門。正準(zhǔn)備干事的時(shí)候,A干完事出去的時(shí)候把門打開了(釋放鎖),房間變成空置狀態(tài)。A和B都很尷尬。

          怎么辦?為了不讓B打擾,A想到了一個(gè)辦法,讓秘書A1每隔10分鐘就把鐘點(diǎn)房的時(shí)間重新設(shè)置成30分鐘。等A事干完了再開門(釋放鎖)。

          為了避免A開了B上的鎖,酒店想出了一個(gè)辦法,指紋上鎖,開鎖的時(shí)候也必須用這個(gè)指紋。這樣A就開不了B上的鎖。

          回到程序,比如A來setnx,默認(rèn)過期時(shí)間30秒,獲取到鎖,但是A比較墨跡,鎖過期(自動(dòng)釋放鎖)的時(shí)候還在執(zhí)行,這時(shí)候B獲取鎖。等A執(zhí)行完來釋放鎖的時(shí)候,其實(shí)釋放的是B的鎖。這個(gè)時(shí)候就需要多一層判斷。A和B,set的my_value必須不一樣(參考故事中的指紋)。當(dāng)A釋放鎖的時(shí)候先判斷是否是自己的鎖,如果是自己的鎖再釋放。可以通過以下Lua腳本實(shí)現(xiàn):

          if?redis.call("get",KEYS[1])?==?ARGV[1]?then
          ????return?redis.call("del",KEYS[1])
          else
          ????return?0
          end

          也可以在自己的業(yè)務(wù)中實(shí)現(xiàn),關(guān)鍵點(diǎn)就是my_value必須唯一,能區(qū)分開A和B。

          至于A的問題,我們另起一個(gè)線程,來監(jiān)控A的過期時(shí)間,每隔10秒鐘就把A的鎖過期時(shí)間設(shè)置成30秒,直到A釋放鎖。

          • 主從模式,單機(jī)版有單點(diǎn)故障,那master-slave應(yīng)該沒問題了吧。master掛了slave頂上。但是請(qǐng)注意,master與slave之間數(shù)據(jù)同步是異步的。就是說master掛了的時(shí)候,可能有寫數(shù)據(jù)并沒有同步到slave。這時(shí)slave成為master的時(shí)候還是丟了鎖。比如A獲取到某資源的鎖,這時(shí)master掛了,恰巧鎖還沒同步到slave。這時(shí)slave晉升為master的時(shí)候并沒有A的鎖,這時(shí)B過來獲取資源鎖的時(shí)候就成功。安全性得不到保障。當(dāng)然如果訪問量小這個(gè)模式完全夠了,哪有那么巧的事。即便是正好趕上,由于訪問量小,彌補(bǔ)也比較容易。

          • 如果訪問量大,對(duì)安全要求高,就得另尋出路。redis官方給出了一個(gè)解決算法。就是Redlock算法。它要求有N組(臺(tái))redis互相獨(dú)立的節(jié)點(diǎn),它們互相獨(dú)立,沒有主從,也沒有集群。客戶端要做的是(以5臺(tái)服務(wù)為例):

          1. 獲取當(dāng)前Unix時(shí)間,以毫秒為單位。
          2. 依次嘗試從N個(gè)實(shí)例,使用相同的key和隨機(jī)值獲取鎖。當(dāng)然向Redis設(shè)置鎖時(shí),客戶端應(yīng)該設(shè)置一個(gè)網(wǎng)絡(luò)連接和響應(yīng)超時(shí)時(shí)間,這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間。例如你的鎖自動(dòng)失效時(shí)間為10秒,則超時(shí)時(shí)間應(yīng)該在5-50毫秒之間。這樣可以避免服務(wù)器端Redis已經(jīng)掛掉的情況下,客戶端還在死死地等待響應(yīng)結(jié)果。如果服務(wù)器端沒有在規(guī)定時(shí)間內(nèi)響應(yīng),客戶端應(yīng)該盡快嘗試另外一個(gè)Redis實(shí)例。
          3. 客戶端使用當(dāng)前時(shí)間減去開始獲取鎖時(shí)間(步驟1記錄的時(shí)間)就得到獲取鎖使用的時(shí)間。當(dāng)且僅當(dāng)從大多數(shù)(N/2+1 )的Redis節(jié)點(diǎn)都取到鎖,并且使用的時(shí)間小于鎖失效時(shí)間時(shí),鎖才算獲取成功。
          4. 如果取到了鎖,key的真正有效時(shí)間等于有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟3計(jì)算的結(jié)果)。
          5. 如果因?yàn)槟承┰颍@取鎖失敗(沒有在至少N/2+1個(gè)Redis實(shí)例取到鎖或者取鎖時(shí)間已經(jīng)超過了有效時(shí)間),客戶端應(yīng)該在所有的Redis實(shí)例上進(jìn)行解鎖。

          上面這段來源redis中文網(wǎng)。我總結(jié)的大概流程是

          1. 依次嘗試從N個(gè)實(shí)例獲取鎖,注意的是獲取鎖的時(shí)間要遠(yuǎn)小于鎖超時(shí)的時(shí)間。
          2. 當(dāng)且僅當(dāng)從大多數(shù)(N/2+1 )的Redis節(jié)點(diǎn)都取到鎖才算成功,否則就是失敗。

          只有當(dāng)N/2+1個(gè)節(jié)點(diǎn)取到鎖才是算成功。釋放鎖比較簡(jiǎn)單,就是釋放每個(gè)節(jié)點(diǎn)的鎖。

          當(dāng)然Redlock解決了分布式鎖的基本問題,別忘了它也有《倆個(gè)房客的故事》中的問題,解決方案和單機(jī)版的redis基本一樣。我們可以基于redis-client原生api來實(shí)現(xiàn)Redlock算法,也可以用一些框架,比如Redisson。

          • Redisson實(shí)現(xiàn)Redlock。語(yǔ)法就比較簡(jiǎn)單
          ????????RLock?rLock1?=?redissonRed1.getLock(lockKey);
          ????????RLock?rLock2?=?redissonRed2.getLock(lockKey);
          ????????RLock?rLock3?=?redissonRed2.getLock(lockKey);
          ????????RedissonRedLock?rLock?=?new?RedissonRedLock(rLock1,rLock2,rLock3);
          ????????rLock.lock();
          ????????try?{
          ??????//搞事情....
          ????????}?finally?{
          ?????????rLock.unlock();
          ????????}

          總結(jié)

          我們介紹了redis鎖的一些基本要求,和常見問題,以及解決方案。當(dāng)然還有其他的問題,望大家一起討論。至于Redlock的實(shí)現(xiàn),建議用框架,可以少care一些細(xì)節(jié)。最后揭秘《倆個(gè)房客的故事》純屬本人虛構(gòu),如有雷同,天理不容。


          瀏覽 37
          點(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>
                  欧美日韩中文字幕第一页 | 九九九精品在线 | 我想看外国操逼大片 | 99热在线观看8 | 激情丁香五月天 |