分散式鎖,是一種思想,它的實現方式有很多。比如,我們將沙灘當做分散式鎖的元件,那麼它看起來應該是這樣的
- 加鎖
在沙灘上踩一腳,留下自己的腳印,就對應了加鎖操作。其他程式或者執行緒,看到沙灘上已經有腳印,證明鎖已被別人持有,則等待。
- 解鎖
把腳印從沙灘上抹去,就是解鎖的過程。
- 鎖超時
為了避免死鎖,我們可以設定一陣風,在單位時間後颳起,將腳印自動抹去。
分散式鎖的實現有很多,比如基於資料庫、memcached、Redis、系統檔案、zookeeper等。它們的核心的理念跟上面的過程大致相同。具備的條件:
1、一個方法同一時間只能被一個機器一個執行緒執行
2、高可用的獲取鎖和釋放鎖
3、高效能的獲取鎖和釋放鎖
4、具備可重入性
5、具備鎖失效機制,防止死鎖
6、具備非阻塞鎖特性,即沒有獲取鎖直接放回獲取鎖失敗。
那麼如何實現分散式鎖呢,有如下幾種方式:
- 基於Zookeeper實現
- 基於快取(redis實現)
- 基於資料庫實現方式
一、基於Zookeeper實現
Zookeeper資料儲存結構是一顆樹,樹由節點組成,節點叫ZNode
Znode分四種型別:
1、持久節點(persistent)
預設節點型別,建立節點的客戶端和Zookeeper斷開連結後,節點依舊存在
2、持久節點順序節點(persistent_sequential)
建立節點時,根據建立時間給節點編號。
3、臨時節點
斷開連結後,節點被刪除
4、臨時順序節點
Zookeeper分散式鎖的原理:
獲取鎖:
- 在Zookeeper建立一個持久節點ParentLock,當客戶端想要獲取鎖時,在ParentLock節點下建立臨時順序節點。
- 然後客戶端再去獲取臨時節點是否是最靠前的一個,如果是則獲取鎖。
- 另外一個客戶端先建立臨時節點,然後獲取臨時節點是靠前,如果不是靠前的,並且不是最小的序號,此時向前面的節點註冊Watcher,用於監聽前一個節點的鎖是否存在。
釋放鎖:
- 任務完成刪除臨時節點
- 由於節點都是相互監聽,當前一個節點消失,下一個節點被置頂
- 如果機器當機了,會自動刪除臨時節點。
缺點:
效能上不如快取服務高,建立鎖和釋放鎖過程上,都動態建立、銷燬臨時節點實現鎖功能,Zk中建立和刪除節點只能通過leader伺服器執行,然後將資料同步到所有follower機器上。
可能會因為網路抖動導致連結中斷,刪除臨時節點,但是ZK有多種重試策略,重試之後才會刪除臨時節點。
二、基於快取(redis實現)
1、加鎖
加鎖實際上就是在redis中,給Key鍵設定一個值,為避免死鎖,並給定一個過期時間。
SET lock_key random_value NX PX 5000
值得注意的是:
random_value 是客戶端生成的唯一的字串。
NX 代表只在鍵不存在時,才對鍵進行設定操作。
PX 5000 設定鍵的過期時間為5000毫秒。
這樣,如果上面的命令執行成功,則證明客戶端獲取到了鎖。
2、解鎖
解鎖的過程就是將Key鍵刪除。但也不能亂刪,不能說客戶端1的請求將客戶端2的鎖給刪除掉。這時候random_value的作用就體現出來。
為了保證解鎖操作的原子性,我們用LUA指令碼完成這一操作。先判斷當前鎖的字串是否與傳入的值相等,是的話就刪除Key,解鎖成功。
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
3、使用
首先,我們在pom檔案中,引入Redis包。在這裡,筆者用的是最新版本,注意由於版本的不同,API可能有所差異。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
加鎖的過程很簡單,就是通過SET指令來設定值,成功則返回;否則就迴圈等待,在timeout時間內仍未獲取到鎖,則獲取失敗。
缺點:
客戶端A從master獲取鎖,master將鎖同步到slave錢,master 當機,slave節點晉升master節點,客戶端B取得了被客戶端A鎖定的同一個資源。安全失效。
三、基於資料庫實現方式
資料庫拍他鎖
1、根據名字獲取鎖資訊
2、更新鎖資訊(比如版本,狀態等)佔有鎖
缺點:
1、資料庫的強依賴性,資料庫不可用會導致業務系統不可用。
2、鎖沒有失效時間,解鎖失敗,會使鎖一直存在,其他執行緒無法獲取鎖。
3、鎖只能是非阻塞,插入失敗報錯,執行緒不會進入排隊佇列中,會再次出發獲取鎖的操作。
4、鎖是非重入,同一個執行緒沒有釋放鎖,無法再次獲得該鎖,因為資料庫中鎖的資料已經存在。
解決方案:
1、主從複製
2、定時任務,或者新增上次更新的時間
3、死迴圈insert
4、資料庫存入當前執行緒的資訊。
四、三種分散式鎖優缺點
分散式鎖 | 優點 | 缺點 |
---|---|---|
Zookeeper | 1、封裝好的框架,容易實現 2、有等待鎖的佇列,提升搶鎖的概率 新增和刪除節點效能低 |
新增和刪除節點效能低 |
Redis | set和del指令效能較高 | 1、實現複雜:需要考慮超時、原子性、誤刪等情況 2、沒有等待鎖的佇列,需要在客戶端自旋等待鎖,效率低 |
資料庫 | 容易理解 | 複雜度較高,需要設計表、策略等如果獲取鎖失敗,需要不斷的連結資料庫,查庫操作。 |