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

          分布式鎖還有這個坑?!

          共 2631字,需瀏覽 6分鐘

           ·

          2022-07-16 14:53

          大家好,我是魚皮,今天分享一篇關(guān)于分布式鎖的文章,3分鐘就能閱讀完,相信對大家會有極大的實戰(zhàn)幫助。

          以下是正文。



          平時在工作中也有用到分布式鎖,但是確實也沒注意到一些臨界值的問題,說白了就是沒有進行深度思考。
          關(guān)于這個標題,你可能會比較詫異,鎖還能怎么優(yōu)雅刪除?直接一個 delete 不就完了,不然還怎么刪除。

          首先啊,我們先簡單說下為什么要分布式鎖,現(xiàn)在基本上都是分布式系統(tǒng),應(yīng)該沒有什么系統(tǒng)是部署的單節(jié)點吧,因為單節(jié)點風(fēng)險比較大,如果節(jié)點宕機,那么整個應(yīng)用就起不來,如果是多節(jié)點,那么好處就多多了,可以做到負載均衡,高可用,即使一個節(jié)點掛了,還有其他節(jié)點可用,服務(wù)整體依然可用。

          分布式雖好,但是也帶來很多問題,比如對于臨界資源的保護,在分布式系統(tǒng)下,自然而然離不開分布式鎖的使用,分布鎖的實現(xiàn)可以用 redis、zookeeper 等,但這些不是我們今天討論的重點。

          以 redis 為例,我們可以使用 redis 的setnx key value來簡單實現(xiàn)一個分布式鎖,它是原子性的,只有當(dāng)value不存在的時候,才會設(shè)置成功,因此用它來實現(xiàn)分布式鎖再好不過。

          但是啊,這里有個問題,代碼的業(yè)務(wù)邏輯復(fù)雜,很多地方有return,那么對應(yīng)的我們是不是要在所有 return 的地方也解鎖。

          setnx lock 1
          if xx {
            delete(lock)
           return 
          }
          if xx {
            delete(lock)
            return 
          }
          ...
          doSomething()
          delete(lock)
          return 

          如果我們不小心在某個地方忘記了刪除(解鎖),那么這個鎖就永遠無法解開了,這就會導(dǎo)致線上事故了。于是我們可以對這個鎖加個過期時間:setex key timeout value,這樣就可以類似做個保底的操作,即使忘了刪除鎖,也可以通過過期時間來降低風(fēng)險。

          因此問題來了,如果鎖時間到了,但是還沒執(zhí)行完邏輯,最后處理完邏輯再正常刪除會導(dǎo)致什么問題?

          1. 剛開始 A、B 節(jié)點都去爭搶鎖setex lock 1 a
          2. A 成功獲得鎖,并且鎖的有效時間是 1s,然后 B 可能自旋等待獲取鎖
          3. 這時 A 由于一些網(wǎng)絡(luò)狀況,導(dǎo)致本來應(yīng)該很快結(jié)束的邏輯,超過了1s才完成,這時鎖自動失效,B獲得鎖(A、B 同時在臨界區(qū))
          4. 由于 A 執(zhí)行完畢邏輯,然后執(zhí)行刪除鎖del lock,這時導(dǎo)致刪除了 B 的鎖,然后 C 獲得鎖 (B、C 同時在臨界區(qū))

          這時是不是發(fā)現(xiàn)了問題所在,造成這個問題的根本原因就是節(jié)點 A 刪錯了鎖,把 B 的鎖給刪了,那如何避免呢?

          延長

          我們可以這樣想,A 的業(yè)務(wù)邏輯還沒走完,就不要放 B 進來,哪怕 A 花了很長時間,所以我們的鎖可以不加過期時間,這樣的話鎖就不會自動消失,但是你要承擔(dān)異常帶來的風(fēng)險:比如我們上面說到的在某個分支判斷處忘了刪除,或者程序還沒走到解鎖的時候異常退出,這些風(fēng)險還是挺高的,那有什么辦法讓鎖有過期時間,也不會在業(yè)務(wù)邏輯還沒走完的時候自動失效呢?答案就是自動延長,比如起一個監(jiān)聽線程,這個線程干兩件事:

          1. 監(jiān)聽鎖的剩余時間
          2. 如果鎖的剩余時間沒多少了,但是業(yè)務(wù)的處理的進度還剩的比較多,嘗試延長時間

          當(dāng)然這個只是個想法,真正實現(xiàn)起來,我覺得相對還是比較不好把握這個“度”的,比如剩余多少時間開始嘗試延長,每次延長多少是個問題,如果延長的時間比較短,那么可能還要幾次延長,延長的比較長可能還比較好,因為可以自己刪除。

          唯一

          我們再來看看另一個更簡單的方法,我們這次不考慮延長鎖的時間了,失效就失效了,只不過我們要確認要刪除的鎖是不是一開始我們上的那一把?那如何確認呢?我們只要在一開始上鎖的時候設(shè)置一個唯一 ID 來替代呆板的“a” (setex lock 1 a),這樣下次準備刪除的時候我們先 check 下這個 value 是不是我們一開始設(shè)置的唯一 ID,如果是的話,說明是我們自己上的那一把,如果不是的話,那么說明鎖在我們執(zhí)行期間失效了,然后給別人上了,我們忽略就行,不需要刪除。

          val = uuid()
          set lock 1 val
          dosomething()
          if redis.get("lock") == val {
           del("lock")
          }

          看著好像沒毛病,但是我們千萬不要忘記原子性這個東西,我們知道 redis 本身處理命令是個單線程,單個指令不可分割,可以保證原子性,但是在此例子中,發(fā)現(xiàn)沒有~我們先 get 判斷了下值,然后再刪除,這整個過程其實不是原子性的,我們看下下面的例子:

          1. 用戶 A 生成 uuid
          2. 用戶 A 獲得鎖,并且鎖的 value 就是 A 的 uuid
          3. 然后 A 處理完自己的邏輯,獲取鎖的 value,發(fā)現(xiàn)是自己的 value
          4. 用戶 B 進來 生成 uuid
          5. 正好鎖的時間到了,鎖自動失效
          6. 用戶 B 上鎖成功
          7. 用戶 A 刪除鎖,尷尬了,還是刪錯了鎖

          造成這個問題的根本原因,就是用戶 A 在讀取 lock 和刪除 lock 這個時間期間被用戶 B 正好插入了進來,從而造成了誤刪,那如何解決這個問題呢?其實也很簡單,redis 提供了 lua 腳本,lua 腳本會被 redis 當(dāng)成一個整體,從而保證原子性,我們只需要把 if get 和 del 用 lua 實現(xiàn)即可,具體 lua 怎么編寫,這里就不細說了。

          ok,這次要說的就這么多,現(xiàn)在回想起來我以前用的很多分布式鎖他們的 value 都是簡單的 1,既沒有考慮到誤刪也沒有考慮到原子的問題,不知道你們有沒有踩過同樣的坑。





          以上就是本期分享了。


          最后,歡迎加入 魚皮的編程知識星球(點擊了解詳情),和大家一起交流學(xué)習(xí)編程,向魚皮和大廠同學(xué) 1 對 1 提問、幫你制定學(xué)習(xí)計劃不迷茫、跟著魚皮直播做項目(往期項目可無限回看)、領(lǐng)取魚皮原創(chuàng)編程學(xué)習(xí)/求職資料等。最近秋招開始了,星球內(nèi)也會幫大家規(guī)劃求職進度、完善簡歷和項目。



          往期推薦

          給大家鼓鼓勁!

          從20s優(yōu)化到500ms,我用了這三招

          遇到個面試題,挺有意思

          盤點程序員逃離一線的Plan B

          Vue 2.7 發(fā)布,代號火影忍者!

          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  日本黄色电影网站wwww | 大鸡巴干的不要不要的视频 | 日韩欧美一级片在线 | 免费久久一级欧美特大黄 | 在线操b 国产乱子伦-区二区三区熟睡91 |