<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
分散式鎖其實就是控制分散式系統不同程序共同存取共用資源的一種鎖的實現。如果不同的系統或同一個系統的不同主機之間共用了某個臨界資源,往往需要互斥來防止彼此干擾,以保證一致性。
一把靠譜的分散式鎖應該有如下特徵:
Redis的分散式鎖最簡單的實現方式為setnx+ expire命令。即先用setnx來搶鎖,如果搶到之後,再用expire給鎖設定一個過期時間,防止鎖忘記了釋放。
SETNX 是SET IF NOT EXISTS的簡寫。日常命令格式是SETNX key value,如果 key不存在,則SETNX成功返回1,如果這個key已經存在了,則返回0。
假設某電商網站的某商品做秒殺活動,key可以設定為key_resource_id,value設定任意值,虛擬碼如下:
if(jedis.setnx(key_resource_id,lock_value) == 1){ //加鎖 expire(key_resource_id,100); //設定過期時間 try { do something //業務請求 }catch(){ } finally { jedis.del(key_resource_id); //釋放鎖 } }
但是這個方案中,setnx和expire兩個命令分開了,不是原子操作。如果執行完setnx加鎖,正要執行expire設定過期時間時,程序crash或者要重啟維護了,別的執行緒永遠獲取不到鎖啦。
為了解決方案一發生異常鎖得不到釋放的場景,有小夥伴認為,可以把過期時間放到setnx的value值裡面。如果加鎖失敗,再拿出value值校驗一下即可。虛擬碼如下:
long expires = System.currentTimeMillis() + expireTime; //系統時間+設定的過期時間 String expiresStr = String.valueOf(expires); // 如果當前鎖不存在,返回加鎖成功 if (jedis.setnx(key_resource_id, expiresStr) == 1) { return true; } // 如果鎖已經存在,獲取鎖的過期時間 String currentValueStr = jedis.get(key_resource_id); // 如果獲取到的過期時間,小於系統當前時間,表示已經過期 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // 鎖已過期,獲取上一個鎖的過期時間,並設定現在鎖的過期時間(不瞭解redis的getSet命令的小夥伴,可以去官網看下哈) String oldValueStr = jedis.getSet(key_resource_id, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 考慮多執行緒並行的情況,只有一個執行緒的設定值和當前值相同,它才可以加鎖 return true; } } //其他情況,均返回加鎖失敗 return false; }
這個方案的優點是,巧妙移除expire單獨設定過期時間的操作,把過期時間放到setnx的value值裡面來。但是這個方案還有別的缺點:
Redis 通過 LUA 指令碼建立具有原子性的命令: 當lua指令碼命令正在執行的時候,不會有其他指令碼或 Redis 命令被執行,實現組合命令的原子操作。
在Redis中執行Lua指令碼有兩種方法:eval和evalsha。eval命令使用內建的 Lua 直譯器,對 Lua 指令碼進行求值,例子如下:
//第一個引數是lua指令碼,第二個引數是鍵名引數個數,剩下的是鍵名引數和附加引數 > eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second"
因此我們可以使用LUA指令碼實現分散式鎖,虛擬碼如下:
//LUA指令碼 if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) else return 0 end; //加鎖 String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" + " redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values)); //判斷是否成功 return result.equals(1L);
Redis的SET指令擴充套件引數也可以保證指令的原子性!
SET key value[EX seconds][PX milliseconds][NX|XX]
NX:表示key不存在的時候,才能set成功,也即保證只有第一個使用者端請求才能獲得鎖,而其他使用者端請求只能等其釋放鎖,才能獲取。
EX seconds:設定key的過期時間,時間單位是秒。
PX milliseconds:設定key的過期時間,單位為毫秒
XX:僅當key存在時設定值
虛擬碼如下:
if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加鎖 try { do something //業務處理 }catch(){ } finally { jedis.del(key_resource_id); //釋放鎖 } }
但是呢,這個方案還是可能存在問題:
既然鎖可能被別的執行緒誤刪,那我們給value值設定一個標記當前執行緒唯一的亂數,在刪除的時候,校驗一下,不就OK了嘛。虛擬碼如下:
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加鎖 try { do something //業務處理 }catch(){ } finally { //判斷是不是當前執行緒加的鎖,是才釋放 if (uni_request_id.equals(jedis.get(key_resource_id))) { jedis.del(lockKey); //釋放鎖 } } }
在這裡,判斷是不是當前執行緒加的鎖和釋放鎖不是一個原子操作。這可能這把鎖已經不屬於當前使用者端,會解除他人加的鎖。
為了更嚴謹,一般也是用lua指令碼代替。lua指令碼如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end;
方案五還是可能存在鎖過期釋放但業務沒執行完的問題。為了解決這個問題,我們可以給獲得鎖的執行緒開啟一個定時守護執行緒,每隔一段時間檢查鎖是否還存在,存在則對鎖的過期時間延長,防止鎖過期提前釋放。
當前開源框架Redisson就是這樣實現的,Redisson底層原理圖如下:
只要執行緒一加鎖成功,就會啟動一個watch dog看門狗,它是一個後臺執行緒,會每隔10秒檢查一下,如果執行緒1還持有鎖,那麼就會不斷的延長鎖key的生存時間。因此,Redisson解決了鎖過期釋放但業務沒執行完的問題。
前面六種方案都只是基於單機版的討論,還不是很完美。其實Redis一般都是叢集部署的:
如果執行緒一在Redis的master節點上拿到了鎖,但是加鎖的key還沒同步到slave節點。恰好這時,master節點發生故障,一個slave節點就會升級為master節點。執行緒二就可以獲取同個key的鎖啦,但執行緒一也已經拿到鎖了,鎖的安全性就沒了。
為了解決這個問題,Redis提出一種高階的分散式鎖演演算法:Redlock。我們假設當前有5個Redis master節點,在5臺伺服器上面執行這些Redis範例,如下圖所示:
則RedLock的實現步驟如下:
到此這篇關於Redis分散式鎖的7種實現的文章就介紹到這了,更多相關Redis分散式鎖內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45