「如何設計」高可用的分佈式鎖

「如何設計」:redis

「如何設計」具有冪等性的服務
「如何設計」微服務中高可用的分佈式鎖算法

分佈式鎖定義

分佈式環境下,鎖定全局惟一公共資源 表現爲:bash

  • 請求串行化
  • 互斥性

第一步是上鎖的資源目標,是鎖定全局惟一公共資源,只有是全局惟一的資源才存在多個線程或服務競爭的狀況,互斥性表現爲一個資源的隔離級別串行化,若是對照單機事務ACID的隔離型來講,互斥性的事務隔離級別是SERLALIZABLE,屬於最高的隔離級別。restful

(事務隔離級別:DEFAULT,READ_UNCOMMITTED,READ_COMMITED,REPEATABLE_READ,SERLALIZABLE)網絡

分佈式鎖目的

  • 解決業務層冪等性
  • 解決MQ消費端屢次接受同一消息
  • 確保串行 | 隔離級別
  • 多臺機器同時執行定時任務

尋找惟一資源進行上鎖

例子:分佈式

1. 防止用戶重複下單 共享資源進行上鎖的對象 : 【用戶id】

2. 訂單生成後發送MQ給消費者進行積分的添加 尋找上鎖的對象 :【訂單id】

3. 用戶已經建立訂單,準備對訂單進行支付,同時商家在對這個訂單進行改價 尋找上鎖對象 : 【訂單id】
複製代碼

基於redis分佈式鎖

redis單線程串行處理自然就是解決串行化問題,用來解決分佈式鎖是再適合不過。微服務

實現方式:
setnx key value Expire_time
獲取到鎖 返回 1 , 獲取失敗 返回 0post

存在問題:性能

鎖時間不可控

redis 只能在setnx指定一個鎖的超時時間,假設初始設定鎖的時間是10秒鐘,可是業務獲取到鎖跑了20秒鐘,在10秒鐘以後,若是又有一個業務能夠獲取到相同的一把鎖,這個時候可能就存在兩個相同的業務都獲取獲得鎖的問題,而且兩個業務處在並行階段。也就是第一個獲取鎖的業務沒法對自身的鎖進行續租。spa

單點鏈接超時問題

redis 的client與server端並無維持心跳的機制,若是在鏈接出現問題,client會獲得一個超時的回饋。

主從問題

redis的集羣實際上在CAP模式中是處在與AP的模型,保證可用性。在主從複製中「主」有數據,但可能「從」尚未數據,這個時候,一旦主掛掉或者網絡抖動等各類緣由,可能會切換到「從」節點,這個時候有可能會致使兩個業務線程同時的獲取到兩把鎖。

image

  1. 業務線程-1 向主節點請求鎖
  2. 業務線程-1 獲取鎖
  3. 業務線程-1 獲取到鎖並開始執行業務
  4. 這個時候redis剛生成的鎖在主從之間還未進行同步
  5. redis這時候主節點掛掉了
  6. redis的從節點升級爲主節點
  7. 業務線程-2 想新的主節點請求鎖
  8. 業務線程-2 獲取到新的主節點返回的鎖
  9. 業務線程-2 獲取到鎖開始執行業務
  10. 這個時候 業務線程-1 和 業務線程-2 同時在執行任務

redlock

上述的問題其實並非redis的缺陷,只是redis採用了AP模型,它自己沒法確保咱們對一致性的要求。redis官方推薦redlock算法來保證,問題是redlock至少須要三個redis主從實例來實現,維護成本比較高,至關於redlock使用三個redis集羣實現了本身的另外一套一致性算法,比較繁瑣,在業界也使用得比較少。

能不能使用redis做爲分佈式鎖

能不能使用redis做爲分佈式鎖,這個自己就不是redis的問題,仍是取決於業務場景,咱們先要本身確認咱們的場景是適合 AP 仍是 CP , 若是在社交發帖等場景下,咱們並無很是強的事務一致性問題,redis提供給咱們高性能的AP模型是很是適合的,但若是是交易類型,對數據一致性很是敏感的場景,咱們可能要尋在一種更加適合的 CP 模型

redis可能做爲高可用的分佈式鎖並不合適,咱們須要確立高可用分佈式鎖的設計目標

高可用分佈式鎖設計目標

  • 強一致性,是CP模型
  • 服務高可用,不存在單點問題
  • 鎖可以續租和自動釋放
  • 業務接入簡單

三種分佈式鎖方案對比

- redis zookeeper etcd
一致性算法 zab raft
CAP AP CP CP
高可用 主從 N+1 N+1
實現 setnx create臨時有序節點 restful

基於zookeeper分佈式鎖

剛剛也分析過,redis其實沒法確保數據的一致性,先來看zookeeper是否合適做爲咱們須要的分佈式鎖,首先zk的模式是CP模型,也就是說,當zk鎖提供給咱們進行訪問的時候,在zk集羣中能確保這把鎖在zk的每個節點都存在。

image

(這個其實是zk的leader經過二階段提交寫請求來保證的,這個也是zk的集羣規模大了的一個瓶頸點)

zk 鎖實現的原理

說zk的鎖問題以前先看看zookeeper中幾個特性,這幾個特性構建了zk的一把分佈式鎖 特性:

  • 有序節點

    當在一個父目錄下如 /lock 下建立 有序節點,節點會按照嚴格的前後順序建立出自節點 lock000001,lock000002,lock0000003,以此類推,有序節點能嚴格保證各個自節點按照排序命名生成。

  • 臨時節點

    客戶端創建了一個臨時節點,在客戶端的會話結束或會話超時,zookepper會自動刪除該解ID那。

  • 事件監聽

    在讀取數據時,咱們能夠對節點設置監聽,當節點的數據發生變化(1 節點建立 2 節點刪除 3 節點數據變成 4 自節點變成)時,zookeeper會通知客戶端。

結合這幾個特色,來看下zk是怎麼組合分佈式鎖。

image

  1. 業務線程-1 業務線程-2 分別向zk的/lock目錄下,申請建立有序的臨時節點
  2. 業務線程-1 搶到/lock0001 的文件,也就是在整個目錄下最小序的節點,也就是線程-1獲取到了鎖
  3. 業務線程-2 只能搶到/lock0002的文件,並非最小序的節點,線程2未能獲取鎖
  4. 業務線程-1 與 lock0001 創建了鏈接,並維持了心跳,維持的心跳也就是這把鎖的租期
  5. 當業務線程-1 完成了業務,將釋放掉與zk的鏈接,也就是釋放了這把鎖

zk分佈式鎖的代碼實現

zk官方提供的客戶端並不支持分佈式鎖的直接實現,咱們須要本身寫代碼去利用zk的這幾個特性去進行實現。

image

zk分佈式鎖客戶端假死的問題

客戶端建立了臨時有序節點並創建了事件監聽,就可讓業務線程與zk維持心跳,這個心跳也就是這把鎖的租期。當客戶端的業務線程完成了執行就把節點進行刪除,也就釋放了這把鎖,不過中間也可能存在問題

  • 客戶端掛掉

    由於註冊的是臨時節點,客戶端掛掉,zk會進行感知,也就會把這個臨時節點刪除,鎖也就隨着釋放

  • 業務線程假死

    業務線程並無消息,而是一個假死狀態,(例如死循環,死鎖,超長gc),這個時候鎖會被一直霸佔不能釋放,這個問題須要從兩個方面進行解決。

    第一個是自己業務代碼的問題,爲什麼會出現死循環,死鎖等問題。

    第二個是對鎖的異常監控問題,這個其實也是微服務治理的一個方面。

zk分佈式鎖 的GC 問題

剛剛說了zk鎖的維持是靠zk和客戶端的心跳進行維持,若是客戶端出現了長時間的GC會出現什麼情況

image

  1. 業務線程-1 獲取到鎖,但未開始執行業務
  2. 業務線程-2 發生長時間的GC
  3. 業務線程-1 和 zk 的心跳發生斷鏈
  4. lock0001 的臨時節點由於心跳斷鏈而被刪除
  5. 業務線程-2 獲取到鎖
  6. 業務線程-2 開始執行業務
  7. 業務線程-1 GC完畢,開始執行業務
  8. 業務線程-1 和 業務線程-2 同時執行業務

基於 etcd 分佈式鎖

etcd分佈式鎖的實現原理

etcd實現分佈式鎖比zk要簡單不少,就是使用key value的方式進行寫入,在集羣中,若是存在key的話就不能寫入,也就意味着不能獲取到鎖,若是集羣中,能夠寫入key,就意味着獲取獲得鎖。

etc到使用了raft保證了集羣的一致性,也就是在外界看來,只要etcd集羣中某一臺機器存在了鎖,全部的機器也就存在了鎖,這個跟zk同樣屬於強一致性,而且數據是能夠進行持久化,默認數據一更新就持久化。

image

鎖的租期續約問題

etcd 並不存在一個心跳的機制,因此跟redis同樣獲取鎖的時候就要對其進行expire的指定,這個時候就存在一個鎖的租期問題。

租期問題有幾種思路能夠去解決,這裏討論其中一種:

在獲取到鎖的業務線程,能夠開啓一個子線程去維護和輪訓這把鎖的有效時間,並定時的對這把鎖進行續租

image

假設業務線程獲取到一把鎖,鎖的expire時間爲10s,業務線程會開啓一個子線程經過輪訓的方式每2秒鐘去把這把鎖進行續租,每次都將鎖的expire還原到10s,當業務線程執行完業務時,會把這把鎖進行刪除,事件完畢。

這種思路同樣會存在問題:

  1. 客戶端掛掉,業務線程和續租子線程都會掛掉,鎖最終會釋放
  2. 業務線程假死,這個跟zk的假死狀況同樣,也是屬於業務代碼應該解決的問題
  3. 客戶端超長GC問題,長GC致使續租子進程沒有進行及時續租,鎖被超時釋放。(GC的問題多是個極端問題,通常GC超過幾秒就可能去查看問題了)

總結

首先得了解清楚咱們使用分佈式鎖的場景,爲什麼使用分佈式鎖,用它來幫咱們解決什麼問題,先聊場景後聊分佈式鎖的技術選型。

不管是redis,zk,etcd其實在各個場景下或多或少都存在一些問題,例如redis的AP模型會限制不少使用場景,但它卻擁有了幾者中最高的性能,zookeeper的分佈式鎖要比redis可靠不少,但他繁瑣的實現機制致使了它的性能不如redis,並且zk會隨着集羣的擴大而性能更加降低。etcd 看似是一種折中的方案,不過像鎖的租期續約都要本身去實現。

簡單來講,先了解業務場景,後進行技術選型。

相關文章
相關標籤/搜索