天天看點

「幹貨」簡單解析分布式鎖的三種實作方式

作者:書香小炒肉

一、前言

在我們的工作以及面試過程中,必不可少的就是會使用或者是被問到分布式鎖。

如果是單機的情況下,需要對一個變量進行多線程的同步通路的時候,可以通過加鎖進行互斥控制,諸如常見的synchronize、lock、volatile、juc包等。

但是在叢集的情況下,請求落在某台機器上是随機的,這使得原來的單機控制側羅失效。這時候就要用到分布式鎖來解決這些問題。

二、分布式鎖的三種實作方式

分布式的CAP理論告訴我們“任何一個分布式系統都無法同時滿足一緻性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多隻能同時滿足兩項。”是以,很多系統在設計之初就要對這三者做出取舍。在網際網路領域的絕大多數的場景中,都需要犧牲強一緻性來換取系統的高可用性,系統往往隻需要保證最終一緻性,隻要這個最終時間是在使用者可以接受的範圍内即可。

下面介紹三種分布式鎖的實作方式:

  • 基于資料庫實作分布式鎖;
  • 基于redis實作分布式鎖;
  • 基于zookeeper實作分布式鎖;

三、基于資料庫實作分布式鎖

1、悲觀鎖

通過加排它鎖來實作,如:select * from tb1 where name='dog' for update

此時的where字段必須加索引,不然會造成鎖表。有時由于表不大,mysql的索引優化器,優化之後sql不走索引,結果也會造成鎖表,這裡要特别注意一下。

但加悲觀鎖一般适合防重的場景,并不适合高并發以及幂等場景。

2、樂觀鎖

悲觀鎖存在性能的問題,為了提升性能,可以考慮使用樂觀鎖來進行實作。

需要在表中增加版本号version或者時間戳temestap字段來實作。

四、基于redis實作

1、使用redis指令實作加鎖

(1)setnx指令

setnx key value:當且僅當key不存在時,set一個key為value的值,傳回1;若key存在,則什麼都不做,傳回0;

(2)expire指令

expire key timeout:為key設定一個過期時間,機關為second,超過這個時間鎖會自動釋放,避免死鎖。

(3)delete指令

delete key:删除key

步驟:擷取鎖的時候,使用setnx指令進行加鎖,并使用expire設定鎖的過期時間,超過過期時間則鎖失效自動釋放,鎖的value是一個唯一值,通過這個在釋放鎖的時候進行判斷;

擷取鎖的時候還要設定一個擷取逾時時間,鎖超過這個時間則放棄擷取這個鎖;

釋放鎖的時候,通過鎖的value值進行判斷是不是該鎖,若是該鎖,則delete進行鎖釋放。

2、基于redission架構實作redis分布式鎖

架構原理如下圖所示:

簡單解析:

對于redis叢集來說,如果某個用戶端需要加鎖,首先會根據hash算法選擇一個節點,通過執行lua腳本進行加鎖。如果需要删除,則執行unlock指令。如果此時用戶端2需要歲該key進行加鎖,則先判斷是否能夠進行加鎖,如果不能則一直進行cas自旋,直到加鎖成功。

watch dog自動延期機制:隻要用戶端1加鎖成功,則背景會自動啟動一個線程開啟watch dog看門狗機制,這個背景程序會每隔10秒檢查一下是否還持有該鎖,如果用戶端1還持有該鎖,則需要進行鎖續命,就會不斷的延長鎖的生存時間,每次續命原來的1/3時間。

可重入鎖機制:如果用戶端1持有了鎖mylock1,結果可重入的加鎖會怎樣?會通過incrby指令,對用戶端1的加鎖次數累加1。mylock1的hash結構中用戶端id後面會對應着加鎖的次數。

如:mylock1{"id:1":2}

釋放鎖機制:執行lock.unlock()進行釋放分布式鎖。每次對鎖資料結構中的加鎖次數減1。如果發現加鎖次數是0了,說明用戶端不再持有該鎖,此時會調用 del mylock1指令,從redis中删除該鎖。而此時用戶端2就可以完成加鎖了。

redisson實作分布式鎖的缺點

注:redisson架構存在最大的缺點是主從架構的鎖失效問題。在master當機的情況下,可能會導緻多個用戶端對同一個key同時完成加鎖。

一般我們redis叢集會采用主從架構,如果我們對某一個redis master加鎖,此時會同步給對應的slave執行個體。但是在這個過程中如果master節點由于網絡或者各種原因當機了,主備切換後,原來的slave節點變成了master節點。如果此時用戶端2嘗試進行加鎖,會在新的master節點上完成加鎖,而此時用戶端1也依然認為自己加鎖成功。此時會導緻多個用戶端為同一個key完成了加鎖。這時在業務上一定會出現問題,導緻各種髒資料的産生。

redisson主從架構鎖失效/重複加鎖問題解決方案

1、可采用redis本身的setnx+expire實作分布式鎖,通過lua腳本進行控制。

2、可采用開源架構RedLock來解決上訴問題。

3、關于RedLock

可以解決主從架構鎖失效、重複加鎖問題,但存在争議。同時還需要處理複雜的復原操作。

其原理如下所示(5主5從):

「幹貨」簡單解析分布式鎖的三種實作方式

注:

加鎖逾時時間的設定要小于鎖失效時間的設定。假如鎖失效的時間為10秒,則逾時時間可設定為50ms,如果逾時,則盡快的跳過,去嘗試下一個master節點。

隻有半數以上的redis節點加鎖成功才算成功加鎖。

redlock的加鎖步驟:

  • 按順序向5個master節點請求加鎖;
  • 根據設定的逾時時間來判斷,是不是要跳過該master節點;
  • 如果大于等于3個節點(半數以上)加鎖成功,并且使用的時間小于鎖的有效期,即可認定加鎖成功;
  • 如果擷取失敗,解鎖;

分布式鎖層面Redis與Zookeeper架構的異同

redis的主節點收到資料以後會立馬傳回,而不用等待複制到從節點,會存在主從節點切換時鎖不一緻以及鎖重複問題,其更多的是保證資料的可用性。

zookeeper的主節點在收到資料後不會立馬傳回,而是要等待過半的從節點同步成功後再通知用戶端,保證資料的一緻性。

五、基于zookeeper實作分布式鎖

一般是基于curator開源架構實作。

架構原理:

「幹貨」簡單解析分布式鎖的三種實作方式

簡單解析:

  • 多個用戶端嘗試加鎖時,首先在鎖節點下面建立一個接一個的臨時節點,放在一個清單中存儲;
  • 用戶端建立臨時節點之後,會判斷自己是不是第一個節點,如果是直接進行加鎖,如果不是則對自己的上一個節點添加監聽事件;隻要上一個節點釋放,則會通知自己已經排到前一位了。
  • 如果某個用戶端建立臨時節點後當機挂掉了,zookeeper會感覺到那個用戶端異常,會自動删除對應的臨時節點,相當于自動釋放鎖,自動取消排隊。

歡迎關注公衆号:

「幹貨」簡單解析分布式鎖的三種實作方式

繼續閱讀