【面试题】Redis中是如何实现分布式锁的
分布式鎖常見的三種實現(xiàn)方式:
- 數(shù)據(jù)庫樂觀鎖;
- 基于Redis的分布式鎖;
- 基于ZooKeeper的分布式鎖。
Redis的分布式鎖
Redis要實現(xiàn)分布式鎖,以下條件應(yīng)該得到滿足
- 互斥性:在任意時刻,只有一個客戶端能持有鎖。
- 不能死鎖:客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續(xù)其他客戶端能加鎖。
- 容錯性:只要大部分的Redis節(jié)點正常運行,客戶端就可以加鎖和解鎖。
實現(xiàn)
可以直接通過 set key value px milliseconds nx 命令實現(xiàn)加鎖, 通過Lua腳本實現(xiàn)解鎖。
//獲取鎖(unique_value可以是UUID等) SET resource_name unique_value NX PX 30000//釋放鎖(lua腳本中,一定要比較value,防止誤解鎖) if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1]) elsereturn 0 end代碼解釋
- set 命令要用 set key value px milliseconds nx,替代 setnx + expire 需要分兩次執(zhí)行命令的方式,保證了原子性
- value 要具有唯一性,可以使用UUID.randomUUID().toString()方法生成,用來標識這把鎖是屬于哪個請求加的,在解鎖的時候就可以有依據(jù);
- 釋放鎖時要驗證 value 值,防止誤解鎖;
- 通過 Lua 腳本來避免 Check And Set 模型的并發(fā)問題,因為在釋放鎖的時候因為涉及到多個Redis操作 (利用了eval命令執(zhí)行Lua腳本的原子性);
加鎖代碼分析
首先,set()加入了NX參數(shù),可以保證如果已有key存在,則函數(shù)不會調(diào)用成功,也就是只有一個客戶端能持有鎖,滿足互斥性。其次,由于我們對鎖設(shè)置了過期時間,即使鎖的持有者后續(xù)發(fā)生崩潰而沒有解鎖,鎖也會因為到了過期時間而自動解鎖(即key被刪除),不會發(fā)生死鎖。最后,因為我們將value賦值為requestId,用來標識這把鎖是屬于哪個請求加的,那么在客戶端在解鎖的時候就可以進行校驗是否是同一個客戶端。
解鎖代碼分析
將Lua代碼傳到j(luò)edis.eval()方法里,并使參數(shù)KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId。在執(zhí)行的時候,首先會獲取鎖對應(yīng)的value值,檢查是否與requestId相等,如果相等則解鎖(刪除key)。
存在的風險
如果存儲鎖對應(yīng)key的那個節(jié)點掛了的話,就可能存在丟失鎖的風險,導(dǎo)致出現(xiàn)多個客戶端持有鎖的情況,這樣就不能實現(xiàn)資源的獨享了。
- 客戶端A從master獲取到鎖
- 在master將鎖同步到slave之前,master宕掉了(Redis的主從同步通常是異步的)。
主從切換,slave節(jié)點被晉級為master節(jié)點 - 客戶端B取得了同一個資源被客戶端A已經(jīng)獲取到的另外一個鎖。導(dǎo)致存在同一時刻存不止一個線程獲取到鎖的情況。
redlock算法出現(xiàn)
這個場景是假設(shè)有一個 redis cluster,有 5 個 redis master 實例。然后執(zhí)行如下步驟獲取一把鎖:
Redis 官方給出了以上兩種基于 Redis 實現(xiàn)分布式鎖的方法,詳細說明可以查看:
https://redis.io/topics/distlock 。
Redisson實現(xiàn)
Redisson是一個在Redis的基礎(chǔ)上實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還實現(xiàn)了可重入鎖(Reentrant Lock)、公平鎖(Fair Lock)、聯(lián)鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)等,還提供了許多分布式服務(wù)。
Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進使用者對Redis的關(guān)注分離(Separation of Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務(wù)邏輯上。
Redisson 分布式重入鎖用法
Redisson 支持單點模式、主從模式、哨兵模式、集群模式,這里以單點模式為例:
// 1.構(gòu)造redisson實現(xiàn)分布式鎖必要的Config Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0); // 2.構(gòu)造RedissonClient RedissonClient redissonClient = Redisson.create(config); // 3.獲取鎖對象實例(無法保證是按線程的順序獲取到) RLock rLock = redissonClient.getLock(lockKey); try {/*** 4.嘗試獲取鎖* waitTimeout 嘗試獲取鎖的最大等待時間,超過這個值,則認為獲取鎖失敗* leaseTime 鎖的持有時間,超過這個時間鎖會自動失效(值應(yīng)設(shè)置為大于業(yè)務(wù)處理的時間,確保在鎖有效期內(nèi)業(yè)務(wù)能處理完)*/boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);if (res) {//成功獲得鎖,在這里處理業(yè)務(wù)} } catch (Exception e) {throw new RuntimeException("aquire lock fail"); }finally{//無論如何, 最后都要解鎖rLock.unlock(); }加鎖流程圖
解鎖流程圖
我們可以看到,RedissonLock是可重入的,并且考慮了失敗重試,可以設(shè)置鎖的最大等待時間, 在實現(xiàn)上也做了一些優(yōu)化,減少了無效的鎖申請,提升了資源的利用率。
需要特別注意的是,RedissonLock 同樣沒有解決 節(jié)點掛掉的時候,存在丟失鎖的風險的問題。而現(xiàn)實情況是有一些場景無法容忍的,所以 Redisson 提供了實現(xiàn)了redlock算法的 RedissonRedLock,RedissonRedLock 真正解決了單點失敗的問題,代價是需要額外的為 RedissonRedLock 搭建Redis環(huán)境。
所以,如果業(yè)務(wù)場景可以容忍這種小概率的錯誤,則推薦使用 RedissonLock, 如果無法容忍,則推薦使用 RedissonRedLock。
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的【面试题】Redis中是如何实现分布式锁的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【工具类】JAVA POI 代码导出表格
- 下一篇: 【MySQL】MySQL开发注意事项与S