分佈式鎖的原理和實現詳解

1、基本概念

分佈式鎖,是單機鎖的一種擴展,主要是爲了鎖住分佈式系統中不一樣機器代碼的物理塊或邏輯塊。以此保證不一樣機器之間的邏輯一致性。

2、一個簡單的案例

對DB寫操做的雙檢鎖案例

  1. 僞代碼以下算法

    if (能夠插入一條數據) {
        lock {
            if (能夠插入一條數據) {
                // 插入一條數據
            }
        }
    }
  2. 上面代碼中的lock若是是單機狀況下的鎖的話,在一臺服務器多線程狀況下是沒有問題的。可是若是是分佈式的狀況下,單機的鎖只能鎖住一臺服務器的物理代碼塊,是沒法防住其餘機器產生的髒數據的。
  3. 分佈式狀況下這個lock就須要換成分佈式鎖以保證數據一致性。

3、分佈式鎖實現原理

所需的依賴

  1. 一個擁有強一致性的服務發現存儲倉庫。(保證數據一致性)
  2. 一個具備高可用性的服務發現存儲倉庫。(保證服務的穩定性)
  3. 在這裏咱們使用etcd做爲服務發現存儲倉庫。

具體實現原理

咱們本次所使用的etcd的能力

  1. etcd節點的保持獨佔能力。(同一時間只有一臺服務器能夠擁有節點)
  2. 節點事件監聽能力。(能夠在節點被釋放/佔有時通知觀察者-服務器)
  3. 節點主動釋放能力。(服務器能夠主動釋放擁有節點)
  4. 節點超時自動釋放能力。(超時自動釋放節點)

在代碼中,須要準備如下幾個內容

  1. etcd長監聽。(監聽etcd節點,在節點變動時做出響應)
  2. 事件隊列。(存放爭奪etcd節點的方法)
  3. 爭奪鎖事件。
  4. 等待鎖超時事件。(沒畫在流程圖中,在爭奪鎖事件入隊列時註冊,持有鎖時註銷)
  5. 持有鎖超時事件。(沒畫在流程圖中,在持有鎖時註冊,在釋放鎖時註銷)
  6. 釋放鎖事件。

基本代碼流程圖

  • 正常代碼流程圖(markdown畫的,有點醜)設計模式

    st=>start: 項目啓動
    watcher=>operation: 監聽etcd
    ed=>end: 等待請求
    
    st->watcher->ed
    st=>start: 開始執行lock
    ed=>end: 結束
    watcher=>operation: 監聽etcd
    hasEvent=>condition: 事件隊列爲空
    pushEvent=>operation: 爭奪鎖事件 入等待隊列
    watcherEvent=>operation: etcd節點超時事件和釋放事件
    popEvent=>operation: 爭奪鎖事件 出事件隊列
    execute=>operation: 執行被鎖住的代碼塊
    fight=>condition: 爭奪鎖失敗
    unlock=>operation: 釋放鎖
    
    st->fight
    fight(no)->execute->unlock->ed
    fight(yes)->pushEvent->watcherEvent->popEvent->fight
  • 事件細節僞代碼緩存

    1. 準備一個清空全部相關事件的萬能方法服務器

      假設eventQueue是這種業務對應的事件隊列
      假設fightEvent是該次執行的爭奪鎖事件
      假設clear爲清除超時事件的方法
      假設holdEvent爲持有鎖超時事件
      假設waitEvent爲等待鎖超時事件
      假設unLockEvent爲釋放鎖事件
      
      // 清空全部事件 - clearAll
      eventQueue.remove(fightEvent)
      clear(waitEvent)
      clear(holdEvent)
    2. 監聽etcd的兩個事件markdown

      // 當etcd鎖超時事件或etcd主動釋放事件發生時
      
      // 爭奪鎖事件 出事件隊列
      fightEvent = eventQueue.pop
      // 執行爭奪鎖事件
      執行 fightEvent
    3. 爭奪鎖事件多線程

      // 爭奪鎖事件
      執行 etcd爭奪鎖方法
      if (搶到鎖了) {
          // 清空全部事件
          執行 clearAll
          // 設置持有鎖超時事件
          設置 holdEvent
      } else {
          // 爭奪鎖事件若是是二次入隊列,建議到隊列頭,而不是到隊列尾
          eventQueue.push(fightEvent)
      }
    4. 等待鎖超時事件(用時間輪延時執行)分佈式

      // 清空全部事件
      執行 clearAll
      // 拋出異常
      throw new Exception
    5. 持有鎖超時事件(用時間輪延時執行)spa

      // 執行釋放鎖事件
      執行 unLockEvent
    6. 釋放鎖事件線程

      // 釋放鎖事件
      執行 etcd釋放鎖方法
      // 清空全部事件
      執行 clearAll
  • 備註設計

    以上代碼能夠用有限狀態機設計模式來設計業務
  • 圖解分佈式服務器與etcd集羣交互

    1. 項目啓動時初始化,服務器A、B、C是一個分佈式系統(不考慮哪臺爲master)
      clipboard.png
    2. 同時發來10個請求,此時通過算法分配假設變成了這樣
      clipboard.png
    3. 開始爭奪鎖時,10個線程同時向etcd爭奪鎖
      clipboard.png
    4. 假設請求2爭奪成功,那麼第一次競爭完畢後除了請求2,其餘請求都進入了各自的事件隊列等待etcd釋放鎖的通知
      clipboard.png
    5. 當請求2執行完代碼塊後,向etcd發送釋放鎖請求
      clipboard.png
    6. etcd收到釋放鎖請求,將節點刪除,觸發compareAndDelete事件(釋放鎖事件)
      clipboard.png
    7. 三臺服務器接收到釋放鎖的信息,各自讓事件隊列頭的請求向etcd發送爭奪鎖請求
      clipboard.png
    8. 鎖搶完後,重複4->6的行爲
      clipboard.png
  • 補充說明

    1. etcd還有一個功能是控制時序,這樣的話就能夠將每一個請求搶鎖的行爲控制到只剩一次,業務執行的順序由etcd控制,不過我沒有試過,有興趣的同窗能夠試一試。
    2. 以前基於分佈式鎖的原理還寫了一個分佈式緩存鎖,是經過鎖緩存來防止緩存擊穿的,有空的時候補上。感謝各位同窗看個人文章看到此處。
相關文章
相關標籤/搜索