5、redis分散式鎖
阿新 • • 發佈:2019-03-24
參考連結:https://www.cnblogs.com/linjiqin/p/8003838.html
一:介紹
實現分散式鎖有三種方式:1、資料庫樂觀鎖,2、基於redis,3、基於zookeeper。 redis服務端是單執行緒操作,完美地避免了多執行緒同步問題,實現了原子性。
首先,為了確保分散式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件: 互斥性。在任意時刻,只有一個客戶端能持有鎖。
不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他客戶端能加鎖。
具有容錯性。只要大部分的Redis節點正常執行,客戶端就可以加鎖和解鎖。
解鈴還須繫鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。
二:實現
1、加鎖
-
1、新版本jedis(2.2.0及以上),set支援判斷是否存在,並且設定過期時間
// 2.2.0及以上版本set支援多引數設定,實現存在判斷以及過期時間設定功能 String result = jedis.set("lockKey", "requesId", "NX", "PX", 20000); if ("OK".equals(result)){ return true; } return false;
-
2、舊版本jedis,使用setnx和expire結合使用 ==> 有缺陷
Long result = jedis.setnx("lockKey", "App1Lock"); if (result == 1) { // 若在這裡程式突然崩潰,則無法設定過期時間,將發生死鎖 jedis.expire("lockKey", 10000); }
-
3、使用Lua指令碼 對 舊版jedis改進。
eval命令執行Lua程式碼的時候,Lua程式碼將被當成一個命令去執行,並且直到eval命令執行完成,Redis才會執行其他命令。所以整體具有原子性 // KEYS是setnxMsg的值,ARGV是expireMsg的值,其實可以在setnxMsg中設定所有的引數,裡面用KEYS[]來取 String script = "if redis.call('setnx', KEYS[1], KEYS[2]) == 1 then return redis.call('expire', ARGV[1], ARGV[2]) else return 0 end"; LinkedList<String> setnxMsg = new LinkedList<>(); LinkedList<String> expireMsg = new LinkedList<>(); setnxMsg.add("lockKey"); setnxMsg.add("app-1-lock"); expireMsg.add("lockKey"); expireMsg.add("20"); // 這個是秒 Object result = jedis.eval(script, setnxMsg, expireMsg); if ("1".equals(result)) { return true; } return false;
2、解鎖
-
1、直接del的問題 ==> 任何人都可以刪除,這不可以
jedis.del("lockKey");
-
2、雖然做了判斷,但是分開執行,還是會有bug
if ("app-1-lockKey".equals(jedis.get("lockKey"))){ // 此時剛好key過期,其他app進行鎖獲取,則會刪除別人的 jedis.del("lockKey"); }
-
3、最可靠的解鎖:lua表示式將判斷和刪除整合在一起執行
String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end"; LinkedList<String> msgList = new LinkedList<>(); msgList.add("lockKey"); msgList.add("app-1-lock"); Object result = jedis.eval(script, msgList, new LinkedList<>()); // 最後引數不能null if ("1".equals(result)) { return true; } return false;