redis setnx

我們在處理業務的時候,有需要控制串行處理的情況,如果是同一個jvm,
我們可以通過java的併發關鍵字 synchronized 來控制,
但是,目前我們的系統基本都是分佈式系統,同一個服務是多臺機器同時提供服務的。
這種情況下,我們就可以使用Redis分佈式鎖;
Redis中有個方法是setnx
SETNX
SETNX key value
將 key 的值設爲 value ,當且僅當 key 不存在。
若給定的 key 已經存在,則 SETNX 不做任何動作。
SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。
可用版本:>= 1.0.0時間複雜度:O(1)返回值:設置成功,返回 1 。設置失敗,返回 0 。
我們在代碼中使用方式
//嘗試設置cachekey看是否能設置成功,設置成功,返回 1 。設置失敗,返回 0
long cnt = CacheUtils.setnx(cachekey, value, 60 * 60);
//如果設置失敗 則直接退出
if (cnt == 0) {
return ;
}
//執行你的業務代碼
// Your Business Code
//最後退出的時候,將這個cachekey 刪掉,讓下一個業務處理單元可以執行該代碼
CacheUtils.del(cachekey);
事情到此還沒有結束。
我們在業務中用了這個方式。但是,發現不起作用。我把我們的業務代碼貼出來,讓大家看問題出在哪兒。
try {
//鎖住 cachekey
long cnt = CacheUtils.setnx(cachekey, tableName, 60 * 60);
if (cnt == 0) {
return result;
}
// 業務代碼 Business Code
// ......
} catch (Exception e) {
LOGGER.error("獲取seq異常{}",e);
result = null;
} finally {
CacheUtils.del(cachekey);
}
看出來問題在哪兒了嗎?
分析了以後,我們才認識到。是finally在作怪,我把修改以後的代碼貼出來
boolean lockedBySelf = false;
try {
if (isLockedByOther(cacheKey,tableName)) {
return result;
}
lockedBySelf = true;
// 業務代碼 Business Code
// ........
} catch (Exception e) {
LOGGER.error("獲取seq異常{}",e);
result = null;
} finally {
if(lockedBySelf){
CacheUtils.del(cacheKey);
}
}
private static final boolean isLookedByOther(String cachekey, String keyName){
return 0 == CacheUtils.setnx(cachekey, keyName, 60 * 60);
}
還沒看出問題的,就往下看
由於有問題的第一段代碼中使用了try-catch-finally 結構
這種結構的特點是: finally 中的代碼必然會被執行。
那麼問題來了。
當有三臺機器同時併發處理的時候,由於其中一個通過setnx 執行成功了,進入邏輯處理階段。
另外兩個當中的一個 進入了try塊,通過setnx執行失敗,於是退出了,
但是因爲已經進入了try塊 ,finally 中的代碼必然會被執行 
( CacheUtils.del(cachekey) 這條語句被順帶執行了)。
剩下來的那個,又可以穩穩當當,好像沒有人跟他併發一樣將事情順其自然地給做了。
後面那段代碼就是在finally中 判斷一下,當前的key是不是被自己鎖的,
如果是自己鎖的,我才能解鎖,否則,沒門

注:有一種情況,當使用setnx時,沒執行到刪除鎖操作時,
出現服務異常退出,會導致鎖程序去除不掉,當然可以設置過期時間,
當出現這種情況交給redis自己解決,
還有種特殊情況當你設置過期時間操作出現異常退出,
這樣就會被永遠鎖住(生產出現過),
由於之前沒用預案不得不手動清理,所以要有定時清理機制,
定時清理會出現併發情況,
最終方案:定時設置過期時間,或者當長時間取不到鎖設置最長獲取不到鎖時間,
通知運維手動清除



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章