分布式鎖(5種)


分布式鎖

1,簡介

傳統的單體應用使用本地鎖(synchronized、reentrantLock),隨着分布式的快速發現者,本地鎖無法解決並發問題,需要一種能跨微服務/跨虛擬機的鎖機制->分布式鎖

作用:

  1. 並發正確性(資源獨占)
  2. 效率:避免重復處理

作用:

  1. 互斥性:基本功能,一個獲取鎖,另外一個就不能獲取
  2. 可重入性能:一個線程獲取到鎖之后,可以再次獲取(多次獲取)
  3. 鎖超時:持有鎖的線程掛掉后,一定時間鎖自動釋放
  4. 高效:加鎖/釋放鎖速度快
  5. 高可用:集群、容災
  6. 支持阻塞和非阻塞
  7. 支持公平鎖和非公平鎖

常用的分布式鎖中間件:

  1. mysql
  2. zookeeper
  3. redis
  4. etcd
  5. chubby

2,分布式鎖

2.1,mysql

方案1:使用專用的數據表

image-20220307192628768

若需要加鎖的資源恰好有對應的數據表,可以在數據表中增加響應的字段,達到服用數據的目的

阻塞式獲取鎖

循環調用lock()函數,直到返回true

image-20220307192808065

非阻塞式獲取鎖

循環調用lock()函數,直到返回true,或者超時

啟動一個定時任務循環遍歷鎖,長時間未被釋放的即為超時,直接刪除

鎖的釋放

image-20220307193010410

適用場景:沒有其他中間件可以使用,需要加鎖的資源恰好有對應的數據表

優點:理解起來簡單,不需要維護其他中間件

缺點:需要自己實現加鎖/解鎖過程,性能較差

2.2,zookeeper

zookeeper是以paxos算法為基礎分布式應用協調服務

image-20220307193554176

data:Znode存儲的數據信息

ACL:記錄Znode的訪問權限

stat:包含Znode的各種元數據

child:子節點(樹狀結構,很像ldap數據倉庫)

image-20220307193734512

鎖的實現原理:

線程去創建/resource_name子節點時會自動編號,第一個編號是/0000001。

第一個線程去創建鎖成功並且發現編號是/0000001並且是最小編號,那就直接保留執行程序;

第二個線程再去獲取鎖時,創建的子節點會自動編號為/0000002,該線程會發現這個節點不是最小節點,就向上一個節點/0000001設置一個watcher監視器,待/0000001線程執行完畢釋放的時候就直接觸發/0000002執行程序;

第三個個線程再去獲取鎖時,創建的子節點會自動編號為/0000003,該線程會發現這個節點不是最小節點,就向上一個節點/000000x設置一個watcher監視器,待/000000x線程執行完畢釋放的時候就直接觸發/0000003執行程序;

天生的公平鎖

image-20220307193838497

加鎖流程:

  • 進行重入的判斷(利用ThreadLocal)
  • 在被鎖資源上建立EPHEMERAL_SEQUENTIAL節點
  • 判斷自己的節點是否位於第一個
    • 若是第一個,則獲取到鎖,返回
    • 若不是第一個,在前一個節點上注冊watcher
  • 進行阻塞等待

解鎖流程:

  • 進行重入的判斷(利用ThreadLocal)
    • 若為重入,在重入次數減1,返回
  • 刪除zookeeper上的有序節點

curator已經實現了上述的zookeeper分布式鎖

image-20220307195010664

優點:

  • 對於鎖超時有現成的處理方法
  • 天然的公平鎖
  • ZK集群保證高可用

缺點:

  • 增加開發與維護成本
  • 性能和MySQL想差不大,依然很差

2.3,chubby

chubby谷歌開發的分布式應用程序協調服務,功能上與zookeeper類似

優點:

  • 創建序列號時,提供了API檢查此序列號是否有效
  • lock-delay,當客戶端失聯的時候,並不會立即釋放鎖(會去真實的確認是否真的失聯)

缺點:

  • 未開源,無法二次開發

2.4,Etcd

Etcd是一個高可用的分布式鍵值(key-value)數據庫,內部采用raft協議作為一致性算法。

特性:

  • lease機制,即租約機制,為存儲的key-value對設置租約,當租約到期,k-v將失效刪除
  • revision機制:每個k帶有revision號,每一次事務加一,全局唯一
  • prefix機制,即前綴機制,也稱為目錄機制
  • watcher機制,即監聽機制,支持watch某個k,也支持watch一個范圍(前綴機制)

image-20220307200048320

原理:

  • /lock/resource為前綴創建key(/lock/resource/nodeX),並設置租約長度
  • 客戶端創建一個定時任務作為“心跳”,定時進行續約(看程序執行的時間,如果耗時長需要去續約)
  • 將創建的key寫入Etcd,獲得revision號
  • 獲取/lock/resource下的所有key
    • 若revision為最小,獲取鎖成功
    • 若非最小,watch前一個revision號,待前面的釋放才獲取到
  • 完成業務后,刪除響應的key釋放鎖

etcdV3已經實現分布式鎖

image-20220307200613258

優點:

  • V3接口提供現場的分布式鎖實現
  • 天然是公平鎖(與zookeeper類似)
  • Etcd集群保證了高可用

缺點:

  • 性能一般

2.5,redis

redis(remote dictionary server)是一個k-v存儲中間件。

實現操作:

redisV2.8之前:使用lua腳本實現,因為setnx命令不支持設置過期ex

redisV2.8之后:set resourceName value ex nx

(ex設置過期時間,ex做獨占操作),這個命令可以保證原子性。以前的版本不能保證原子性!

image-20220307201202839

加鎖問題:

  • 進程A未續約(設置有效期),導致B獲取了鎖

  • 復雜操作需要及時續約:expire resourceName

image-20220307201333766

解鎖問題:

  • 進程B解鎖時,key已經被A刪除,導致B異常

  • 解鎖時需要判斷是否是自身持有的鎖

image-20220307201457559

使用業務代碼判斷,判斷和刪除非原子操作,有安全問題(前面判斷在了,后面就刪除,但是這兩個操作之間有可能就被其他線程B獲取到鎖了!!!)

image-20220307201656220

使用lua腳本判斷,判斷和刪除是原子操作

redisson封裝了鎖的實現

繼承了java.util.concurrent.locks.Lock接口

實現3種:阻塞式的(lock),非阻塞式(tryLock),異步非阻塞式的(tryLockAsync)

image-20220307201846351

實現原理:

嘗試加鎖,首先會嘗試進行加鎖,由於保證操作是原子性,那么就只能使用lua腳本,相關的lua腳本如下:

image-20220307202425013

redisson並沒使用set nx,而是使用hash結構

原理:

  • 如果嘗試獲取鎖失敗,判斷是否超時,如果超時則返回false
  • 如果加鎖失敗之后,沒有超時,那么需要在名字為redissopnm_name_channel+lockName的channel上進行訂閱,用於訂閱解鎖消息,然后一直阻塞直到超時,或者有解鎖消息
  • 重試上述步驟,直到最后獲取到鎖,或者某一步獲取鎖超時
  • 解鎖時通過lua腳本,如果是可重入鎖,只是減1;如果是非加鎖線程解鎖,那么解鎖失敗

image-20220307204017401

redLock紅鎖

redis主從與集群並不是強一致性的,所以在極端情況下,會有一致性問題,若redis未及時持久化,重啟會丟失數據。為了解決上述問題,redis作者提出了RedLock紅鎖算法。

原理:

  • 首先生成多個redis集群的Rlock,並將其構造程RedLock
  • 依次循環對三個集群進行加鎖,加鎖方式和redission一致
  • 如果循環加鎖的過程中加鎖失敗,那么需要判斷加鎖失敗的次數是否超出了最大值(要多數成功)
  • 加鎖的過程中需要判斷是否加鎖超時
  • 若失敗,向所有節點請求解鎖

image-20220307204433258

進程A依次向master1,master2,master3獲取鎖

優點:

  • redis在項目中很常見
  • 容易取得可靠性和性能的平衡

缺點:

  • RedLock算法需要多套redis實例,資源耗費

3,安全問題

3.1,GC導致鎖超時

線程A獲取到鎖,正常情況下程序1秒執行完畢,然后釋放鎖;但是突然系統來了一個stop-the-world GC pause耗時2秒鍾,此時鎖已經自動釋放,線程A恢復運行;這時線程B是可以獲取到鎖!(線程不安全)

image-20220307205026934

chubby lock-delay:當客戶端失聯的時候,並不會立即釋放鎖,而是在一定時間內(默認1min)阻止其他客戶端拿到這個鎖

3.2,網絡I/O導致鎖超時

與上面的GC類似,網絡不穩定,請求某些接口耗時特別長導致這個事務整體耗時變長,分布式鎖超時釋放了!

image-20220307220336843

chubby:提供API,供storage服務在收到請求時校驗當前序號,如果查詢獲取到當前釋放的鎖已經被過期了那么就直接拒絕!

3.3,時鍾跳躍導致的鎖超時

從NTP服務收到了一個大的時鍾更新,導致一大批鎖直接過期!

解決辦法:少量多次更新時間,例如更新時間是10分鍾,我們分為10次,每次更新1分鍾,來逐步更新系統時間,這樣相對會好一些

參考鏈接:

5種最常見的分布式鎖中間件精講

paxos算法

Paxos、Raft分布式一致性算法應用場景


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM