傳統單點應用的線程安全問題,因為只涉及到單個應用中多線程之間的資源共享,往往通過加鎖synchronized,ReentrantReadWriteLock等手段就能實現共享資源的安全;

但是現在很多大型系統,高並發的量往往較大,整個服務架構也都是設計成分散式架構,原本在單機中的共享資源轉而分佈在了不同的機器上,這個時候鎖也應該相應的升級為分散式鎖;

分散式鎖有多種方式redis,zookeepper等,因為系統中本就用到了redis,就以redis為例:

分散式鎖需要滿足什麼條件呢?

一,互斥:既然是鎖,不能每個客戶端都有吧,那還鎖啥?

二,不能死鎖:讓一個客戶端抱住鎖,永遠不釋放,別的就獲取不到了,那還要分散式系統幹啥?

三,每個客戶端的鎖自己加,自己解;

redis為什麼能作為分散式鎖的選擇呢?本身就是單進程單線程的模式,並且提供的命令具有原子性;

一般用來做分散式鎖的方式有1,setnx+getset方式, 2,INCR

1,setnx+getSet方法加鎖:

setnx:(set if not exists)不存在就設置,設置成功為1,已經存在,返回0;

getSet: set新key,並返回原來的value;

偽代碼如下圖:

場景解釋如下:

第一個線程讀到沒有鎖存在,使用lock加鎖,帶上時間戳,超時則釋放鎖,防止死鎖

第二個線程讀到鎖存在(setnx==0),進入循環,判斷鎖是否在超時時間內,並使用setGet方法把最新的超時時間set進去,(防止超時時間到了,多個線程讀到鎖超時,都去釋放的情況),

第一個線程處理完業務邏輯,並刪除鎖,則後面的線程可以獲得鎖。。。

2,INCR方法加鎖

這個方法會在不存在key的時候,先初始化0,然後加1,返回1;如果key存在,則在執行就會大於1;

所以使用INCR方法加鎖,偽代碼如下圖:

1,進行加鎖操作;

2,如果鎖不存在,並且加鎖成功,設置過期時間;

3,如果鎖已經存在,查看是否已經過了過期時間,如果過了,則重新設置。。

當然上述的分散式鎖只在單機redis中安全,如果存在redis集羣,可能會因為宕機,延時等問題,讓鎖不在唯一。需要redis官方推薦的redlock進行加鎖,對此reddssion已經有良好的封裝RedissionLock,建議可直接使用。

最近會持續的分享JAVA開發相關的技術,強化自己的技術,也方便大家找工作!更多的技術分享,敬請關注。。


用redis處理高並發是個很常見的方式,因為redis的訪問效率很高(直接訪問內存),一般我們會用來處理網站一瞬間的並發量。

那如果要使用redis來進行高並發問題的解決的話,應注意以下幾點:

1、首先我們要先知道,我們在存儲時,應使用redis的setnx方法,不應該使用set方法,因為setnx擁有原子操作命令(正確點說法應該是使用setnx,根據其屬性可以保證共享資源的原子性操作),當資源鎖存在不能設置值,則返回0,而當鎖不存在,則設置鎖,返回1; 但如果使用set方法,則會出現在高並發情況下,進程同時獲取鎖狀態為null,同時設置,鎖之間相互覆蓋,但是倆進程仍在並發執行業務代碼的情況。

2、為了防止死鎖,我們不應直接使用jedis.setnx(lock, 1) 來進行簡單的加鎖,這樣會導致當進程執行出現問題,鎖未釋放,則其他進程永遠處於阻塞狀態,出現死鎖。 為了避免死鎖,我們在加鎖時帶上時間戳,setnx(lock, 時間戳+超時時間),當發現鎖超時了,便可以對鎖進行重置,避免死鎖。

接下來,實際操作!

設置鎖:

//其中currentTimeMullis為當前時間、valideTime為超時時間,key為資源

//對該資源進行鎖獲取,如果存在鎖則會返回false,不存在則設置值並返回true

boolean lock = redisService.setnx(key, currentTimeMullis+valideTime);

//如果不存在並設置了值,則可以直接返回,因為已經獲取資源鎖成功

//否則,則代表存在這個鎖,則進行鎖是否超時的判斷。獲取該資源的鎖時間,用於判斷是否超時了

String keyTime = redisService.get(key);

if((Long.valueOf(currentTimeMullis)-Long.valueOf(keyTime))>valideTime){

//該判斷代表該資源鎖已經超時,那麼便進行資源鎖的重置,也就是進行資源鎖的重新設置(刪除並重新設置)

//重新設置成功後也返回,因為獲取鎖成功,可以進行操作啦。

}

//如果以上操作都沒有成功,則返回失敗,代表獲取鎖失敗,不可以進行操作。

釋放鎖:

當對資源處理結束後,則調用釋放方法釋放鎖資源

(經提醒,我發現我這裡少了個判斷邏輯...)

//在刪除前,應該先對該資源鎖進行獲取,判斷值與此時釋放鎖的線程所攜帶的值是否相等,也就是我們上面創建時用的currentTimeMullis+valideTime。

String keyLockTime = redisService.get(key);

if(keyLockTime!=null&&keyLockTime.equals(currentTimeMullis+valideTime)){

//此時鎖還由當前線程保持則釋放鎖

redisService.del(key);

}else{

//此時說明該資源鎖被其他線程重置或釋放,已不再擁有鎖的釋放權

//結束

}

——沒事待在家裡不出門的 居家程序員。(我不想脫髮!)


推薦閱讀:
相關文章