分散式鎖解決因為刪鎖而產生的兩種問題
設定鎖過期時間,訪問資料庫後刪除鎖。涉及的兩種問題都是毫秒級別的問題,為了減輕伺服器的壓力,一般在大廠裡要完善的程式碼。
而小公司裡,則不需要這麼麻煩,不設計刪除鎖,直接等待過期時間過去自動刪除鎖。這樣就省去了因為刪除鎖產生的一系列問題。
分散式鎖的兩種問題:
第一種問題:鎖過期,刪除鎖,把別人的刪了
第1條執行緒拿鎖進去訪問資料庫,突然該執行緒發生了意外,等待時間較長,直到過期時間到了(鎖釋放,第2條執行緒拿鎖進入訪問)!突然:第1條執行緒復活,訪問資料庫出來,刪除鎖(此時把第2條執行緒的鎖給刪除了)
請設計方案解決這個問題,怎麼防止誤刪別人的鎖?
UUID 的概念
UUID(Universally Unique Identifier):通用唯一識別碼,是一種軟體建構的標準。
UUID是指在一臺機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。
雖然設定這把鎖key是唯一的(sku:skuId:lock),但是可以設定value不唯一:
採用UUID.randomUUID().tostring()生成隨機數。
每設定一把鎖都是不同的value,根據不同的value識別唯一的鎖
set一把鎖,再get這一把鎖,就可以獲得value值(相當於這把鎖的密碼,用UUID隨機生成獨一無二的密碼)
答:第1條執行緒如果要刪除鎖,get一下key的鎖(第2條執行緒設定的鎖),雖然它是同一個key,但是value卻是唯一的。
這樣就實現了避免誤刪別人的鎖了。
String token = UUID.randomUUID().toString();
//每設定一把鎖都是不同的value,根據不同的value識別唯一的鎖
String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);
拿鎖訪問資料庫,省略。。。
String lockToken = jedis.get("sku:" + skuId + ":lock");
//get鎖的value值,自己設定的則刪除。get別人鎖的value值,則不刪除!
if(StringUtils.isNotBlank(lockToken)&&lockToken.equals(token)){
jedis.del("sku:" + skuId + ":lock");
//訪問資料庫結束後,刪除鎖。下一個使用者繼續拿鎖訪問!
}
第二種問題:get到key鎖相等UUID的value(允許刪鎖),鎖過期,刪除鎖,把別人的刪了。
在一個問題的基礎上,使用UUID雖然可以解決問題,但是在刪除鎖的時候!
假設第1條執行緒get到value值是自己的(可以刪除鎖),但正在做判斷if的時候,鎖剛好過期,鎖釋放!鎖一釋放,第2條執行緒就拿到鎖了。
這時第1條執行緒做完if判斷了,進入刪除鎖,又把第2條執行緒的鎖刪了!!!
怎樣解決這個問題?
使用lua指令碼,保證執行程式碼段的原子性,get到鎖相等的value就刪除,就是一個殺手,逮到就刪,否則不刪。
使用lua指令碼替代第一個問題刪除鎖的三個步驟(get查詢,if判斷,del刪除)
是第一個問題的進階版本
String token = UUID.randomUUID().toString();
//每設定一把鎖都是不同的value,根據不同的value識別唯一的鎖
String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);
拿鎖訪問資料庫,省略。。。
//使用lua指令碼刪除鎖
//KEYS[1]對應第一個singletonList查鎖的value,ARGV[1]對應第二個singletonList的token-UUID值。
//如果value==token,則刪除!否則不刪!
String script ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//訪問資料庫結束後,刪除鎖。下一個使用者繼續拿鎖訪問!
jedis.eval(script, Collections.singletonList("sku:" + skuId + ":lock"),Collections.singletonList(token));
這裡就不測試了,這裡取最優lua指令碼刪除鎖的第二種方法寫進程式碼