分散式鎖實現方案(REDIS,ZOOKEEPER,TAIR)
Zookeeper
1、原生ZK方案
Zookeeper中有一種節點叫做順序節點,假如我們在/lock/目錄下建立節3個點,ZooKeeper叢集會按照提起建立的順序來建立節點,節點分別為/lock/0000000001、/lock/0000000002、/lock/0000000003。
ZooKeeper中還有一種名為臨時節點的節點,臨時節點由某個客戶端建立,當客戶端與ZooKeeper叢集斷開連線,則開節點自動被刪除。
EPHEMERAL_SEQUENTIAL為臨時順序節點
實現分散式鎖的基本邏輯:
- 客戶端呼叫create()方法建立名為“locknode/guid-lock-”的節點,需要注意的是,這裡節點的建立型別需要設定為EPHEMERAL_SEQUENTIAL。
- 客戶端呼叫getChildren(“locknode”)方法來獲取所有已經建立的子節點。
- 客戶端獲取到所有子節點path之後,如果發現自己在步驟1中建立的節點是所有節點中序號最小的,那麼就認為這個客戶端獲得了鎖。
- 如果建立的節點不是所有節點中需要最小的,那麼則監視比自己建立節點的序列號小的最大的節點,進入等待。直到下次監視的子節點變更的時候,再進行子節點的獲取,判斷是否獲取鎖。
釋放鎖的過程相對比較簡單,就是刪除自己建立的那個子節點即可。
以下是流程圖:
讀寫鎖:讀寫鎖的實現與互斥鎖類似,不同的地方在於建立自節點時讀鎖和寫鎖要區分型別。例如讀鎖的字首可以設定為read,寫鎖的字首可以設定為write。建立讀鎖的時候,檢查是否有編號小於自己的寫鎖存在,若存在則對編號剛好小於自己的寫鎖節點進行監聽。建立寫鎖時,檢查建立的節點編號是否為最小,如不是最小,則需要對編號剛好小於自己的節點進行監聽(此時不區分讀鎖和寫鎖)
2、Curator方案
封裝了zk的客戶端,其分散式實現方式和上面的基本相同。同時還提供了不同的鎖型別:
可重入鎖:實現類為InterProcessMutex,將執行緒物件,節點,鎖物件相關聯。InterProcessMutex內部維護了一個使用執行緒為key,{thread,path}為值的map,所以對不同的執行緒和請求加鎖的節點進行一一對應。提供方法acquire 和 release。
不可重入鎖:實現類為InterProcessSemaphoreMutex,類似InterProcessMutex,只是沒有維護執行緒的map。
可重入讀寫鎖:類似JDK的ReentrantReadWriteLock
.一個讀寫鎖管理一對相關的鎖。 主要由兩個類實現:
- InterProcessReadWriteLock
- InterProcessLock
使用時首先建立一個InterProcessReadWriteLock
例項,然後再根據你的需求得到讀鎖或者寫鎖, 讀寫鎖的型別是InterProcessLock
。
讀寫鎖的實現與互斥鎖類似,不同的地方在於建立自節點時讀鎖和寫鎖要區分型別。例如讀鎖的字首可以設定為read,寫鎖的字首可以設定為write。建立讀鎖的時候,檢查是否有編號小於自己的寫鎖存在,若存在則對編號剛好小於自己的寫鎖節點進行監聽。建立寫鎖時,檢查建立的節點編號是否為最小,如不是最小,則需要對編號剛好小於自己的節點進行監聽(此時不區分讀鎖和寫鎖)
還有訊號量和多鎖物件。
3、menagerie方案
menagerie基於Zookeeper實現了java.util.concurrent包的一個分散式版本。這個封裝是更大粒度上對各種分散式一致性使用場景的抽象。其中最基礎和常用的是一個分散式鎖的實現:
org.menagerie.locks.ReentrantZkLock,通過ZooKeeper的全域性有序的特性和EPHEMERAL_SEQUENTIAL型別znode的支援,實現了分散式鎖。
Redis
最常見互斥鎖方案:
- C3傳送SETNX lock.{orderid} 想要獲得鎖,由於C0還持有鎖,所以Redis返回給C3一個0,
- C3傳送GET lock.{orderid} 以檢查鎖是否超時了,如果沒超時,則等待或重試。
- 反之,如果已超時,C3通過下面的操作來嘗試獲得鎖:
GETSET lock.{orderid} <current Unix time + lock timeout + 1> - 通過GETSET,C3拿到的時間戳如果仍然是超時的,那就說明,C3如願以償拿到鎖了。
- 如果在C3之前,有個叫C4的客戶端比C3快一步執行了上面的操作,那麼C3拿到的時間戳是個未超時的值,這時,C3沒有如期獲得鎖,需要再次等待或重試。留意一下,儘管C3沒拿到鎖,但它改寫了C4設定的鎖的超時值,不過這一點非常微小的誤差帶來的影響可以忽略不計。
-
# get lock
-
lock = 0
-
while lock != 1:
-
timestamp = current Unix time + lock timeout + 1
-
lock = SETNX lock.orderid timestamp
-
if lock == 1 or (now() > (GET lock.orderid) and now() > (GETSET lock.orderid timestamp)):
-
break
-
else:
-
sleep(10ms)
-
-
do_your_job()
-
-
# release lock
-
if now() < GET lock.orderid:
-
DEL lock.orderid
Tair
設計思路和Medis類似,但實現略有不同。
美團維護的Tair中增加了expireLock和expireUnlock介面,通過鎖狀態和過期時間戳來共同判斷鎖是否存在:只有鎖已經存在且沒有過期的狀態才判定為有鎖狀態。在有鎖狀態下,不能加鎖,能通過大於過期時間的時間戳進行解鎖;在無鎖狀態下,可以加鎖,加鎖成功會返回過期時間戳,用於解鎖使用。重要的是,expireLock的原子性可以保證加鎖和解鎖時不會因為執行緒搶佔引起錯誤。
不可重入鎖:在加鎖時呼叫expireLock,解鎖時呼叫expireUnlock介面。傳入的引數為過期時間或者過期時間戳。可以防止當執行緒拿到鎖之後阻塞或者當機,鎖可以在過期之後釋放出來。同時可以滿足解鎖動作安全,當自己的鎖過期時不會誤刪別人的鎖。
可重入鎖:類似不可重入鎖,維護類似zk的一個執行緒數和鎖名的map。
可重入讀寫鎖:
讀執行緒:先用當前時間進行一次解鎖expireUnlock,如果能解開則說明沒有執行緒在寫,可以進行讀操作,同時incr,將計數器加1;完成讀之後進行decr。
寫執行緒:getCount讀取計數器,如果為0,則說明沒有執行緒在讀,否則則需要等待;再expireLock,如果成功說明獲取到了寫鎖,否則則說明已經有執行緒在寫了;完成寫之後進行解鎖expireUnlock
缺陷:均有兩步操作,但無法保證原子性。
相關文章
- 使用redis和zookeeper實現分散式鎖Redis分散式
- Redis、Zookeeper實現分散式鎖——原理與實踐Redis分散式
- 十九、Redis分散式鎖、Zookeeper分散式鎖Redis分散式
- 分散式鎖之Zookeeper實現分散式
- 分散式鎖實現(二):Zookeeper分散式
- ZooKeeper分散式鎖的實現分散式
- 6 zookeeper實現分散式鎖分散式
- 基於redis和zookeeper的分散式鎖實現方式Redis分散式
- Redis實現分散式鎖的幾種方案Redis分散式
- 分散式鎖----Redis實現分散式Redis
- Redis實現分散式鎖Redis分散式
- 【Redis】利用 Redis 實現分散式鎖Redis分散式
- 基於資料庫、redis和zookeeper實現的分散式鎖資料庫Redis分散式
- 二十二、zookeeper實現分散式鎖分散式
- zookeeper 分散式鎖的原理及實現分散式
- 基於 Zookeeper 的分散式鎖實現分散式
- Zookeeper分散式鎖實現Curator十一問分散式
- Java分散式鎖方案和區別 - Redis,Zookeeper,資料庫Java分散式Redis資料庫
- Redis之分散式鎖實現Redis分散式
- 分散式鎖之Redis實現分散式Redis
- redis分散式鎖-java實現Redis分散式Java
- Redis如何實現分散式鎖Redis分散式
- 分散式鎖實現(一):Redis分散式Redis
- 利用Redis實現分散式鎖Redis分散式
- redis分散式鎖的實現Redis分散式
- redis分散式鎖-SETNX實現Redis分散式
- 使用 Redis 實現分散式鎖Redis分散式
- 【zookeeper】zookeeper分散式鎖分散式
- Redis面試系列:Redis實現分散式鎖Redis面試分散式
- 分散式鎖的實現方案分散式
- redis實現分散式id方案Redis分散式
- 實現一個 Redis 分散式鎖Redis分散式
- Redis優雅實現分散式鎖Redis分散式
- 基於redis實現分散式鎖Redis分散式
- 如何用 Redis 實現分散式鎖Redis分散式
- 用 Go + Redis 實現分散式鎖GoRedis分散式
- redis分散式鎖實現(golang版)Redis分散式Golang
- 如何使用Redis實現分散式鎖Redis分散式