面試被問Redis和zk兩種分佈式鎖的對比

 

1、基於數據庫實現分佈式鎖

1. 悲觀鎖mysql

利用select … where … for update 排他鎖redis

注意: 其餘附加功能與實現一基本一致,這裏須要注意的是「where name=lock 」,name字段必需要走索引,不然會鎖表。有些狀況下,好比表不大,mysql優化器會不走這個索引,致使鎖表問題。算法

2. 樂觀鎖sql

所謂樂觀鎖與前邊最大區別在於基於CAS思想,是不具備互斥性,不會產生鎖等待而消耗資源,操做過程當中認爲不存在併發衝突,只有update version失敗後才能覺察到。數據庫

咱們的搶購、秒殺就是用了這種實現以防止超賣。緩存

經過增長遞增的版本號字段實現樂觀鎖mysql優化

2、基於緩存(Redis等)實現分佈式鎖

一、官方叫作 RedLock 算法,是 redis 官方支持的分佈式鎖算法。併發

這個分佈式鎖有 3 個重要的考量點:異步

  • 1.互斥(只能有一個客戶端獲取鎖)
  • 2.不能死鎖
  • 3.容錯(只要大部分 redis 節點建立了這把鎖就能夠)

二、下面是redis分佈式鎖的各類實現方式和缺點,按照時間的發展排序分佈式

一、直接setnx

直接利用setnx,執行完業務邏輯後調用del釋放鎖,簡單粗暴

缺點:若是setnx成功,還沒來得及釋放,服務掛了,那麼這個key永遠都不會被獲取到

二、setnx設置一個過時時間

爲了改正第一個方法的缺陷,咱們用setnx獲取鎖,而後用expire對其設置一個過時時間,若是服務掛了,過時時間一到自動釋放

缺點:setnx和expire是兩個方法,不能保證原子性,若是在setnx以後,還沒來得及expire,服務掛了,仍是會出現鎖不釋放的問題

三、set nx px

redis官方爲了解決第二種方式存在的缺點,在2.8版本爲set指令添加了擴展參數nx和ex,保證了setnx+expire的原子性,使用方法:set key value ex 5 nx

缺點:

  • 若是在過時時間內,事務尚未執行完,鎖提早被自動釋放,其餘的線程仍是能夠拿到鎖
  • 上面所說的那個缺點還會致使當前的線程釋放其餘線程佔有的鎖

四、加一個事務id

上面所說的第一個缺點,沒有特別好的解決方法,只能把過時時間儘可能設置的長一點,而且最好不要執行耗時任務

第二個缺點,能夠理解爲當前線程有可能會釋放其餘線程的鎖,那麼問題就轉換爲保證線程只能釋放當前線程持有的鎖。

即setnx的時候將value設爲任務的惟一id,釋放的時候先get key比較一下value是否與當前的id相同,是則釋放,不然拋異常回滾,其實也是變相地解決了第一個問題

缺點:get key和將value與id比較是兩個步驟,不能保證原子性

五、set nx px + 事務id + lua

咱們能夠用lua來寫一個getkey並比較的腳本,jedis/luttce/redisson對lua腳本都有很好的支持

缺點:集羣環境下,對master節點申請了分佈式鎖,因爲redis的主從同步是異步進行的,master在內存中寫入了nx以後直接返回,客戶端獲取鎖成功。

此時master節點掛了,而且數據還沒來得及同步,另外一個節點被升級爲master,這樣其餘的線程依然能夠獲取鎖。

六、redlock

爲了解決上面提到的redis集羣中的分佈式鎖問題,redis的做者antirez的提出了red lock的概念,假設集羣中全部的n個master節點徹底獨立,而且沒有主從同步。

此時對全部的節點都去setnx,而且設置一個請求過時時間re和鎖的過時時間le,同時re必須小於le(能夠理解,否則請求3秒纔拿到鎖,而鎖的過時時間只有1秒也太蠢了)。

此時若是有n / 2 + 1個節點成功拿到鎖,這次分佈式鎖就算申請成功。

缺點:可靠性尚未被普遍驗證,而且嚴重依賴時間,好的分佈式系統應該是異步的,並不能以時間爲擔保,程序暫停、系統延遲等均可能會致使時間錯誤。

3、基於zookeeper實現的分佈式鎖

1. 實現方式

ZooKeeper是一個爲分佈式應用提供一致性服務的開源組件,它內部是一個分層的文件系統目錄樹結構,規定同一個目錄下只能有一個惟一文件名。基於ZooKeeper實現分佈式鎖的步驟以下:

  1. 建立一個目錄mylock;
  2. 線程A想獲取鎖就在mylock目錄下建立臨時順序節點;
  3. 獲取mylock目錄下全部的子節點,而後獲取比本身小的兄弟節點,若是不存在,則說明當前線程順序號最小,得到鎖;
  4. 線程B獲取全部節點,判斷本身不是最小節點,設置監聽比本身次小的節點;
  5. 線程A處理完,刪除本身的節點,線程B監聽到變動事件,判斷本身是否是最小的節點,若是是則得到鎖。

這裏推薦一個Apache的開源庫Curator,它是一個ZooKeeper客戶端,Curator提供的InterProcessMutex是分佈式鎖的實現,acquire方法用於獲取鎖,release方法用於釋放鎖。

優勢:具有高可用、可重入、阻塞鎖特性,可解決失效死鎖問題。

缺點:由於須要頻繁的建立和刪除節點,性能上不如Redis方式。

2. 兩種利用特性實現原理:

一、利用臨時節點特性

zookeeper的臨時節點有兩個特性,一是節點名稱不能重複,二是會隨着客戶端退出而銷燬,所以直接將key做爲節點名稱,可以成功建立的客戶端則獲取成功,失敗的客戶端監聽成功的節點的刪除事件

缺點:全部客戶端監聽同一個節點,可是同時只有一個節點的事件觸發是有效的,形成資源的無效調度

二、利用順序臨時節點特性

zookeeper的順序臨時節點擁有臨時節點的特性,同時,在一個父節點下建立建立的子臨時順序節點,會根據節點建立的前後順序,用一個32位的數字做爲後綴。

咱們能夠用key建立一個根節點,而後每次申請鎖的時候在其下建立順序節點,接着獲取根節點下全部的順序節點並排序,獲取順序最小的節點,若是該節點的名稱與當前添加的名稱相同。

則表示可以獲取鎖,不然監聽根節點下面的處於當前節點以前的節點的刪除事件,若是監聽生效,則回到上一步從新判斷順序,直到獲取鎖。

總結

基於數據庫分佈式鎖實現

優勢:直接使用數據庫,實現方式簡單。

缺點:

  1. db操做性能較差,而且有鎖表的風險
  2. 非阻塞操做失敗後,須要輪詢,佔用cpu資源;
  3. 長時間不commit或者長時間輪詢,可能會佔用較多鏈接資源

基於redis緩存

  1. redis set px nx + 惟一id + lua腳本

優勢:redis自己的讀寫性能很高,所以基於redis的分佈式鎖效率比較高

缺點:依賴中間件,分佈式環境下可能會有節點數據同步問題,可靠性有必定的影響,若是發生則須要人工介入

  1. 基於redis的redlock

優勢:能夠解決redis集羣的同步可用性問題

缺點:

  1. 依賴中間件,並無被普遍驗證,維護成本高,須要多個獨立的master節點;須要同時對多個節點申請鎖,下降了一些效率
  2. 鎖刪除失敗 過時時間很差控制
  3. 非阻塞,操做失敗後,須要輪詢,佔用cpu資源;

基於zookeeper的分佈式鎖

優勢:不存在redis的超時、數據同步(zookeeper是同步完之後才返回)、主從切換(zookeeper主從切換的過程當中服務是不可用的)的問題,可靠性很高

缺點:依賴中間件,保證了可靠性的同時犧牲了一部分效率(可是依然很高)。性能不如redis。

jdk的方式不太推薦。

  1. 從理解的難易程度角度(從低到高)數據庫 > 緩存 > Zookeeper
  2. 從實現的複雜性角度(從低到高)Zookeeper >= 緩存 > 數據庫
  3. 從性能角度(從高到低)緩存 > Zookeeper >= 數據庫
  4. 從可靠性角度(從高到低)Zookeeper > 緩存 > 數據庫

沒有絕對完美的實現方式,具體要選擇哪種分佈式鎖,須要結合每一種鎖的優缺點和業務特色而定。

 

相關文章
相關標籤/搜索