目錄
一 互斥鎖
二 分布式鎖
Redis實現分布式鎖
redisson實現分布式鎖
可重入性:
主從一致性(性能差):
一 互斥鎖
假設我們現在有一個業務要實現秒殺優惠券的功能,如果是一個正常的流程,線程之間應該是這樣運作的。
線程1先查詢優惠券,如果庫存充足,那么扣減庫存,然后線程2來查詢優惠券信息,如果充足,那么就扣減庫存,但是這只是理想的情況。
那么如果出現圖2的這種情況呢?
線程1在查詢優惠券信息的時候發現庫存是充足的,線程2查詢的時候也是充足的,他倆都可以進行減庫存的操作,假如優惠券只剩1張了,那么誰得到這個優惠券呢?這就是業務中可能出現的問題。
此時就需要用互斥鎖來解決問題了
如圖,假如線程1在獲取互斥鎖以后,線程2來了之后就只能發起獲取鎖的請求,只有當線程1操作完了之后釋放鎖,線程2獲取到鎖,才可以進行接下來的操作,這樣就不會出現庫存超賣的情況。
但是還有一個問題,上面所說的這種情況是針對單個Redis服務器進行加鎖的,但是實際的業務邏輯中可能會有多個用戶,訪問多個Redis服務器,那么這時候要怎么解決呢?
二 分布式鎖
假設我的代碼部署到了多個tomcat中,每一個tomcat操控一個JVM,此時8080的虛擬機知道8081的線程1也在同時訪問優惠券信息嗎?這顯然是不知道的。
此時就需要用到一個獨立于tomcat之外的分布式鎖來進行判斷
如圖,當8080的線程1進行訪問時,其余的端口和線程都不可以進行訪問,此時就達到了分布式鎖的效果,有效的解決了超賣問題。要注意,分布式鎖是加在Redis上的,不是自己的代碼中。
Redis實現分布式鎖
Redis實現分布式鎖主要是利用Redis的setnx命令
SET LOCK VALUE NX EX 10 //加鎖
DEL key //釋放鎖
第一條代碼是加鎖并且給鎖設置過期時間,第二行代碼是刪除鎖。如果不給鎖設置過期時間,那么就有可能產生死鎖的現象。即線程1執行時間過長,線程2一直在等待鎖釋放。
redisson實現分布式鎖
redisson中也有分布式鎖的實現
RLock lock = redissonClient.getLock("lock");
try{boolean isLock = lock.tryLock(10,TimeUnit.SECONDS);if(isLock){System.out.println("執行業務");}
} finally{lock.unlock();}
首先,線程1獲取到鎖之后,然后去操作Redis,也會去通知看門狗系統,而看門狗系統會每隔釋放時間(redisson默認30秒)/3去給鎖續期,希望業務完成之前不要因為鎖到期而引發線程安全問題,此時線程1執行完,線程2獲取到鎖就可以繼續執行了。而加鎖和設置過期時間都是基于lua腳本完成的,這樣可以保證操作的原子性。
可重入性:
假設下圖是線程1所執行的代碼,在redis當中會按照哈希結構去進行存儲,key為鎖的key,value會存儲一個鍵值對,鍵為線程名稱,value為鎖的次數默認為0,加一次鎖就+1,釋放一次鎖就-1.
![]() | ![]() |
如上圖,執行add1( )時,Thread1第一次加鎖時,value會被改成1,當add2( )想獲取鎖時,此時Redis會進行判斷,你是不是線程1來的,發現add2( )的線程名為Thread1,那么此時該鎖可以重入的,value值會變成2,當add2( )執行完以后,釋放鎖,value又變為1,add1( )執行完以后,value變成0,此時的鎖才是真正被線程1所釋放了。
主從一致性(性能差):
如圖,如果是在集群模式下的Redis服務器勢必會有主節點和從節點,當線程1過來獲取到一把鎖時,主節點剛好宕機了,集群又重新選了一個主節點,線程2此時又獲取到了和線程1同樣的一把鎖,這樣就會產生鎖沖突的現象。
針對這樣的情況就要加紅鎖(RedLock):在多個Redis實例上加鎖,而不是只在一個節點上進行加鎖,這樣就可以避免鎖重復。
但是這樣實現相對來說比較復雜,因此AP思想和CP思想(zookeeper)可以解決該問題。
所以說性能和復雜是負相關的。