redis做分布式鎖可能不那麼簡單

2021-09-21 02:14:18 字數 2869 閱讀 2641

一、為什麼需要分布式鎖

隨著網際網路的興起,現代軟體發生了翻天覆地的變化,以前單機的程式,已經支撐不了現代的業務。無論是在抗壓,還是在高可用等方面都需要多台計算機協同工作來解決問題。現代的網際網路系統都是分布式部署的,分布式部署確實能帶來效能和效率上的提公升,但為此,我們就需要多解決乙個分布式環境下,資料一致性的問題。

當某個資源在多系統之間共享的時候,為了保證大家訪問這個資源資料是一致的,那麼就必須要求在同一時刻只能被乙個客戶端處理,不能併發的執行,否者就會出現同一時刻有人寫有人讀,大家訪問到的資料就不一致了。

因此,為了解決這個問題,我們就必須引入「分布式鎖」。分布式鎖,是指在分布式的部署環境下,通過鎖機制來讓多客戶端互斥的對共享資源進行訪問。分布式鎖的特點如下:

1、互斥性

和我們本地鎖一樣互斥性是最基本,但是分布式鎖需要保證在不同節點的不同執行緒的互斥。

2、可重入性

同乙個節點上的同乙個執行緒如果獲取了鎖之後那麼也可以再次獲取這個鎖。

3、鎖超時

和本地鎖一樣支援鎖超時,防止死鎖。

4、高效,高可用

加鎖和解鎖需要高效,同時也需要保證高可用防止分布式鎖失效,可以增加降級。

5、支援阻塞和非阻塞

和 reentrantlock 一樣支援 lock 和 trylock 以及 trylock(long timeout)。

二、基於redis分布式鎖

如果你通過網路搜尋分布式鎖,最多的就是基於redis的了。基於redis的分布式鎖得益於redis的單執行緒執行機制,單執行緒在執行上就保證了指令的順序化,所以很大程度上降低了開發人員的思考設計成本。但是,基於redis做分布式鎖難道真的這麼容易嗎?
1、原子操作

基於redis的分布式鎖常用命令是

setnx key value

只在鍵 key 不存在的情況下,將鍵 key的值設定為value 。若鍵key 已經存在, 則setnx 命令不做任何動作。setnx 是『set if not exists』(如果不存在,則 set)的簡寫。**示例:

redis> setnx redislock 「redislock」 # redislock 設定成功

(integer) 1

redis> setnx redislock 「redislock2」 # 嘗試覆蓋 redislock ,失敗

(integer) 0

redis> get redislock # 沒有被覆蓋

「redislock」

成功獲取到鎖之後,然後設定乙個過期時間(這裡避免了客戶端down掉,鎖得不到釋放的問題)

redis> expire redislock 5

成功拿到鎖的客戶端順利進行自己的業務,業務**執行完,然後再刪除該key

redis> del redislock

如果一切都想想象的那麼順利,程式設計師***就不用996了。假如客戶端拿到鎖之後,執行設定超時指令之前down掉了(現實總是那麼悲劇),那這個鎖就永遠都釋放不了.也許你會想到用 redis 事務來解決。但是這裡不行,因為 expire 是依賴於 setnx 的執行結果的,如果 setnx 沒搶到鎖,expire 是不應該執行的。事務裡沒有 if-else 分支邏輯,事務的特點是一口氣執行,要麼全部執行要麼乙個都不執行。公司幾個億的業務又被你耽誤了…

以上情況的出現是因為兩個命令並非乙個原子性操作,所以在redis 2.8 版本之後出現了新的命令
setex key seconds value

所以現在可以利用一條原子性操作的命令來獲取鎖

redis> setex redislock 60 redislock

okredis> get redislock # 值

「redislock」

redis> ttl redislock # 剩餘生存時間

(integer) 49

2、超時問題

在正常的業務當中,當乙個執行緒獲取到鎖並且設定了鎖的過期時間之後,會出現由於業務**執行時間過長,鎖由於到達超時時間自動釋放的情況。自動釋放之後,其他的執行緒就會獲取到分布式鎖,導致業務**不會序列執行。如果業務上允許這樣的情況偶爾發生,那程式設計師就開幹吧,最後頂多人工干預一下,update 一下資料庫。

為了避免這類情況發生,在使用redis分布式鎖的時候,業務方應盡量避免長時間執行的**任務。

如果設定鎖的超時時間比較長,在一定程度上可以緩解業務**執行時間長鎖自動到期的問題,但是一旦業務**down掉,其他等待鎖的執行緒等待的時間會比較長,這種情況下,確保獲取到鎖的程式不會down 成為了主要問題。

3、獲取鎖失敗

當鎖被乙個呼叫方獲取之後,其他呼叫方在獲取鎖失敗之後,是繼續輪詢還是直接業務失敗呢?如果是繼續輪詢的話,同步情況下當前執行緒會一直處於阻塞狀態,所以這裡輪詢的情況還是建議使用非同步。

4、可重入性

可重入性是指已經擁有鎖的客戶端再次請求加鎖,如果鎖支援同乙個客戶端重複加鎖,那麼這個鎖就是可重入的。如果基於redis的分布式鎖要想支援可重入性,需要客戶端封裝,可以使用threadlocal儲存持有鎖的資訊。這個封裝過程會增加**的複雜度,所以菜菜不推薦這樣做。
5、redis掛了

如果在多個客戶端獲取鎖的過程中,redis 掛了怎麼辦呢?假如乙個客戶端已經獲取到了鎖,這個時候redis掛了(假如是redis集群),其他的redis伺服器會接著提供服務,這個時候其他客戶端可以在新的伺服器上獲取到鎖了,這也導致了鎖意義的丟失。有興趣的同學可以去看看redlock,這種方案以犧牲效能的代價解決了這個問題。
6、時鐘跳躍問題

在某些時候,redis的伺服器時間發生的跳躍,由於鎖的過期時間依賴於伺服器時間,所以也會出現兩個客戶端同時獲取到鎖的情況發生。

Redis做分布式鎖

在分布式系統中,在介面沒 冪等性或者在某些場景下相同的服務需要有且僅有乙個服務執行的情況下,需要使用分布式鎖來保證系統的安全執行。分布式鎖的執行順序,有服務a,分別部署了三個節點為a1 a2 a3,為滿足我們上述需求,我們需要在共享的資料載體中做標記,即,a1開始執行的話,a2 a3不能在執行,直到...

Redis做分布式鎖

在分布式系統中,在介面沒 冪等性或者在某些場景下相同的服務需要有且僅有乙個服務執行的情況下,需要使用分布式鎖來保證系統的安全執行。分布式鎖的執行順序,有服務a,分別部署了三個節點為a1 a2 a3,為滿足我們上述需求,我們需要在共享的資料載體中做標記,即,a1開始執行的話,a2 a3不能在執行,直到...

redis分布式鎖

redis分布式鎖 直接上 我寫了四個redis分布式鎖的方法,大家可以提個意見 第一種方法 redis分布式鎖 param timeout public void lock long timeout thread.sleep 100 catch exception e override publi...