redis分布式鎖的這些坑,我懷疑你是假的開發


摘要:用鎖遇到過哪些問題?

一、白話分布式

什么是分布式,用最簡單的話來說,就是為了較低單個服務器的壓力,將功能分布在不同的機器上面;就比如:

本來一個程序員可以完成一個項目:需求->設計->編碼->測試

但是項目多的時候,一個人也扛不住,這就需要不同的人進行分工合作了

這就是一個簡單的分布式協同工作了;

二、分布式鎖

首先看一個問題,如果說某個環節被終止或者別侵占,就會發生不可知的事情

這就會出現,設計好的或者設計的半成品會被破壞,導致后面環節出錯;

這時候,我們就需要引入分布式鎖的概念;

何為分布式鎖?

  • 當在分布式模型下,數據只有一份(或有限制),此時需要利用鎖的技術控制某一時刻修改數據的進程數。
  • 用一個狀態值表示鎖,對鎖的占用和釋放通過狀態值來標識。

分布式鎖的條件:

  • 可以保證在分布式部署的應用集群中,同一個方法在同一時間只能被一台機器上的一個線程執行。
  • 這把鎖要是一把可重入鎖(避免死鎖)
  • 這把鎖最好是一把阻塞鎖
  • 這把鎖最好是一把公平鎖
  • 有高可用的獲取鎖和釋放鎖功能
  • 獲取鎖和釋放鎖的性能要好

分布式鎖的實現:

分布式鎖的實現由很多種,文件鎖、數據庫、redis等等,比較多,在實踐中,還是redis做分布式鎖性能會高一些;

三、redis實現分布式鎖

首先看兩個命令:

setnx:將 key 的值設為 value,當且僅當 key 不存在。 若給定的 key 已經存在,則 SETNX 不做任何動作。 SETNX 是SET if Not eXists的簡寫。

127.0.0.1:6379> set lock "unlock"
OK
127.0.0.1:6379> setnx lock "unlock"
(integer) 0
127.0.0.1:6379> setnx lock "lock"
(integer) 0
127.0.0.1:6379> 

expire: EXPIRE key seconds

為給定 key 設置生存時間,當 key 過期時(生存時間為 0 ),它會被自動刪除

127.0.0.1:6379> expire lock 10
(integer) 1
127.0.0.1:6379> ttl lock
8
127.0.0.1:6379> get lock
(nil)

基於分布式鎖的流程:

這就是一個簡單的分布式鎖的實現流程,具體代碼實現也很簡單,就不贅述了;

四、redis實現分布式鎖問題

如果出現了這么一個問題:如果setnx是成功的,但是expire設置失敗,那么后面如果出現了釋放鎖失敗的問題,那么這個鎖永遠也不會被得到,業務將被鎖死?

解決的辦法:使用set的命令,同時設置鎖和過期時間

set參數:

set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:設置失效時長,單位秒
PX milliseconds:設置失效時長,單位毫秒
NX:key不存在時設置value,成功返回OK,失敗返回(nil)
XX:key存在時設置value,成功返回OK,失敗返回(nil)

實踐:

127.0.0.1:6379> set unlock "234" EX 100 NX
(nil)
127.0.0.1:6379> 
127.0.0.1:6379> set test "111" EX 100 NX
OK

這樣就完美的解決了分布式鎖的原子性。

五、用鎖遇到過哪些問題?又是如何解決的?

未關閉資源

由於當前線程 獲取到redis 鎖,處理完業務后未及時釋放鎖,導致其它線程會一直嘗試獲取鎖阻塞,例如:用Jedis客戶端會報如下的錯誤信息

1redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

redis線程池已經沒有空閑線程來處理客戶端命令。使用原生方法記得關閉!

解決的方法也很簡單,只要我們細心一點,拿到鎖的線程處理完業務及時釋放鎖

B的鎖被A給釋放了

我們知道Redis實現鎖的原理在於 SETNX命令。當 key不存在時將 key的值設為 value ,返回值為 1;若給定的 key已經存在,則 SETNX不做任何動作,返回值為 0 。

SETNX key value

我們來設想一下這個場景:A、B兩個線程來嘗試給key myLock加鎖,A線程先拿到鎖(假如鎖3秒后過期),B線程就在等待嘗試獲取鎖,到這一點毛病沒有。

那如果此時業務邏輯比較耗時,執行時間已經超過redis鎖過期時間,這時A線程的鎖自動釋放(刪除key),B線程檢測到myLock這個key不存在,執行 SETNX命令也拿到了鎖。

但是,此時A線程執行完業務邏輯之后,還是會去釋放鎖(刪除key),這就導致B線程的鎖被A線程給釋放了。

為避免上邊的情況,一般我們在每個線程加鎖時要帶上自己獨有的value值來標識,只釋放指定value的key,否則就會出現釋放鎖混亂的場景

一般我們可以設置value為業務前綴_當前線程ID或者uuid,只有當前value相同的才可以釋放鎖

鎖過期了,業務還沒執行完

redis分布式鎖過期,而業務邏輯沒執行完的場景,不過,這里換一種思路想問題,把redis鎖的過期時間再弄長點不就解決了嗎?

那還是有問題,我們可以在加鎖的時候,手動調長redis鎖的過期時間,可這個時間多長合適?業務邏輯的執行時間是不可控的,調的過長又會影響操作性能。

要是redis鎖的過期時間能夠自動續期就好了。

為了解決這個問題我們使用redis客戶端redisson,redisson很好的解決了redis在分布式環境下的一些棘手問題,它的宗旨就是讓使用者減少對Redis的關注,將更多精力用在處理業務邏輯上。

redisson對分布式鎖做了很好封裝,只需調用API即可。

1  RLock lock = redissonClient.getLock("stockLock");

redisson在加鎖成功后,會注冊一個定時任務監聽這個鎖,每隔10秒就去查看這個鎖,如果還持有鎖,就對過期時間進行續期。默認過期時間30秒。這個機制也被叫做:“看門狗”

redis主從復制的坑

redis高可用最常見的方案就是主從復制(master-slave),這種模式也給redis分布式鎖挖了一坑。

redis cluster集群環境下,假如現在A客戶端想要加鎖,它會根據路由規則選擇一台master節點寫入key mylock,在加鎖成功后,master節點會把key異步復制給對應的slave節點。

如果此時redis master節點宕機從節點復制失敗,為保證集群可用性,會進行主備切換,slave變為了redis master。B客戶端在新的master節點上加鎖成功,而A客戶端也以為自己還是成功加了鎖的。另外如果主從復制延遲同樣也會造成加鎖和解鎖延遲的問題。

此時就會導致同一時間內多個客戶端對一個分布式鎖完成了加鎖,導致各種臟數據的產生。

畢竟redis是保持的AP而非CP,如果要追求強一致性可以使用zookeeper分布式鎖。

本文分享自華為雲社區《redis分布式鎖?易踩得坑》,原文作者:minjie 。

 

點擊關注,第一時間了解華為雲新鮮技術~


免責聲明!

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



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