一文帶你搞懂Redis分布式鎖
1、分布式鎖簡介
分布式鎖是控制分布式系統(tǒng)不同進(jìn)程共同訪問共享資源的一種鎖的實(shí)現(xiàn)。如果不同的系統(tǒng)或同一個(gè)系統(tǒng)的不同主機(jī)之間共享了某個(gè)臨界資源,往往需要互斥來防止彼此干擾,以保證一致性。
業(yè)界流行的分布式鎖實(shí)現(xiàn),一般有這3種方式:
- 基于數(shù)據(jù)庫實(shí)現(xiàn)的分布式鎖
- 基于Redis實(shí)現(xiàn)的分布式鎖
- 基于Zookeeper實(shí)現(xiàn)的分布式鎖
這里主要介紹如何通過 Redis 來實(shí)現(xiàn)分布式鎖。在介紹 Redis 分布式鎖之前,我們首先介紹一下實(shí)現(xiàn)Redis 分布式鎖的關(guān)鍵命令。
2、setnx
setnx key value
Setnx(SET if Not eXists) 命令在指定的 key 不存在時(shí),為 key 設(shè)置指定的值。
設(shè)置成功,返回 1 。設(shè)置失敗,返回 0 。
PS:Redis 官方是不推薦基于 setnx 命令來實(shí)現(xiàn)分布式鎖的,因?yàn)闀?huì)存在很多問題,
①、單點(diǎn)問題。比如:
1、客戶端A 從master拿到鎖lock01
2、master正要把lock01同步(Redis的主從同步通常是異步的)給slave時(shí),突然宕機(jī)了,導(dǎo)致lock01沒同步給slave
3、主從切換,slave節(jié)點(diǎn)被晉級(jí)為master節(jié)點(diǎn)
4、客戶端B到master拿lock01照樣能拿到。這樣必將導(dǎo)致同一把鎖被多人使用。
②、鎖的高級(jí)用法,比如讀寫鎖、可重入鎖等等,setnx 都比較難實(shí)現(xiàn)。
這里先介紹基于 sentnx 實(shí)現(xiàn)的分布式鎖,后面會(huì)介紹官方推薦的基于 redisson 來實(shí)現(xiàn)分布式鎖。
3、Redis-分布式鎖-階段1
接到上文,查詢?nèi)?jí)分類數(shù)據(jù),如果我們部署了多個(gè)商品服務(wù),然后多個(gè)線程同時(shí)去獲取三級(jí)分類數(shù)據(jù),如果不加分布式鎖,就會(huì)導(dǎo)致,每一個(gè)部署的商品服務(wù)第一次查詢都會(huì)走 DB。
public?Map<String,?List<Catelog2Vo>>?getCatelogJsonWithRedisLock()?throws?InterruptedException?{ ????//?一、獲取分布式鎖 ????Boolean?lock?=?stringRedisTemplate.opsForValue().setIfAbsent("lock",?"111"); ????if(lock){ ????????//?true?表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù) ????????Map<String,?List<Catelog2Vo>>?dataFromDb?=?getDataFromDb(); ????????stringRedisTemplate.delete("lock"); ????????return?dataFromDb; ????}else{ ????????System.out.println("獲取分布式鎖失敗...等待重試..."); ????????//加鎖失敗...重試機(jī)制 ????????//休眠一百毫秒 ????????try?{ ????????????TimeUnit.MILLISECONDS.sleep(100); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????????//自旋的方式 ????????return?getCatelogJsonWithRedisLock(); ????} }
4、Redis-分布式鎖-階段2
設(shè)置鎖自動(dòng)過期
public?Map<String,?List<Catelog2Vo>>?getCatelogJsonWithRedisLock()?throws?InterruptedException?{ ????//?一、獲取分布式鎖 ????Boolean?lock?=?stringRedisTemplate.opsForValue().setIfAbsent("lock",?"111"); ????if(lock){ ????????//?true?表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù) ????????//?設(shè)置過期時(shí)間 ????????stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS); ????????Map<String,?List<Catelog2Vo>>?dataFromDb?=?getDataFromDb(); ????????stringRedisTemplate.delete("lock"); ????????return?dataFromDb; ????}else{ ????????System.out.println("獲取分布式鎖失敗...等待重試..."); ????????//加鎖失敗...重試機(jī)制 ????????//休眠一百毫秒 ????????try?{ ????????????TimeUnit.MILLISECONDS.sleep(100); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????????//自旋的方式 ????????return?getCatelogJsonWithRedisLock(); ????} }
5、Redis-分布式鎖-階段3
setnx 命令和過期時(shí)間保證原子性。
public?Map<String,?List<Catelog2Vo>>?getCatelogJsonWithRedisLock()?throws?InterruptedException?{ ????//?一、獲取分布式鎖 ????Boolean?lock?=?stringRedisTemplate.opsForValue().setIfAbsent("lock",?"111",30,TimeUnit.SECONDS); ????if(lock){ ????????//?true?表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù) ????????//?設(shè)置過期時(shí)間 ????????//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS); ????????Map<String,?List<Catelog2Vo>>?dataFromDb?=?getDataFromDb(); ????????stringRedisTemplate.delete("lock"); ????????return?dataFromDb; ????}else{ ????????System.out.println("獲取分布式鎖失敗...等待重試..."); ????????//加鎖失敗...重試機(jī)制 ????????//休眠一百毫秒 ????????try?{ ????????????TimeUnit.MILLISECONDS.sleep(100); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????????//自旋的方式 ????????return?getCatelogJsonWithRedisLock(); ????} }
6、Redis-分布式鎖-階段4
保證刪除的是自己的鎖。
public?Map<String,?List<Catelog2Vo>>?getCatelogJsonWithRedisLock()?throws?InterruptedException?{ ????//?一、獲取分布式鎖 ????String?uuid?=?UUID.randomUUID().toString(); ????Boolean?lock?=?stringRedisTemplate.opsForValue().setIfAbsent("lock",?uuid,30,TimeUnit.SECONDS); ????if(lock){ ????????//?true?表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù)? ????????//?設(shè)置過期時(shí)間 ????????//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS); ????????Map<String,?List<Catelog2Vo>>?dataFromDb?=?getDataFromDb(); ????????String?lockValue?=?stringRedisTemplate.opsForValue().get("lock"); ????????if(uuid.equals(lockValue)){ ????????????stringRedisTemplate.delete("lock"); ????????} ????????return?dataFromDb; ????}else{ ????????System.out.println("獲取分布式鎖失敗...等待重試..."); ????????//加鎖失敗...重試機(jī)制 ????????//休眠一百毫秒 ????????try?{ ????????????TimeUnit.MILLISECONDS.sleep(100); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????????//自旋的方式 ????????return?getCatelogJsonWithRedisLock(); ????} }
7、Redis-分布式鎖-階段5
通過Lua腳本保證刪除鎖和判斷鎖兩個(gè)操作原子性
public?Map<String,?List<Catelog2Vo>>?getCatelogJsonWithRedisLock(){ ????//?一、獲取分布式鎖 ????String?uuid?=?UUID.randomUUID().toString(); ????Boolean?lock?=?stringRedisTemplate.opsForValue().setIfAbsent("lock",?uuid,30,TimeUnit.SECONDS); ????if?(lock)?{ ????????System.out.println("獲取分布式鎖成功..."); ????????Map<String,?List<Catelog2Vo>>?dataFromDb?=?null; ????????try?{ ????????????//加鎖成功...執(zhí)行業(yè)務(wù) ????????????dataFromDb?=?getDataFromDb(); ????????}?finally?{ ????????????String?script?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end"; ????????????//刪除鎖 ????????????stringRedisTemplate.execute(new?DefaultRedisScript<Long>(script,?Long.class),?Arrays.asList("lock"),?uuid); ????????} ????????//先去redis查詢下保證當(dāng)前的鎖是自己的 ????????//獲取值對(duì)比,對(duì)比成功刪除=原子性?lua腳本解鎖 ????????//?String?lockValue?=?stringRedisTemplate.opsForValue().get("lock"); ????????//?if?(uuid.equals(lockValue))?{ ????????//?????//刪除我自己的鎖 ????????//?????stringRedisTemplate.delete("lock"); ????????//?} ????????return?dataFromDb; ????}else{ ????????System.out.println("獲取分布式鎖失敗...等待重試..."); ????????//加鎖失敗...重試機(jī)制 ????????//休眠一百毫秒 ????????try?{ ????????????TimeUnit.MILLISECONDS.sleep(100); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????????//自旋的方式 ????????return?getCatelogJsonWithRedisLock(); ????} }
這也是分布式鎖的最終模式,需要保證兩個(gè)點(diǎn):加鎖【設(shè)置鎖+過期時(shí)間】和刪除鎖【判斷+刪除】原子性。
到此這篇關(guān)于一文帶你搞懂Redis分布式鎖的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
手把手帶你實(shí)現(xiàn)第一個(gè)Mybatis程序
這篇文章主要介紹了mybatis實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-07-07springboot加載命令行參數(shù)ApplicationArguments的實(shí)現(xiàn)
本文主要介紹了springboot加載命令行參數(shù)ApplicationArguments的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04java自帶的MessageDigest實(shí)現(xiàn)文本的md5加密算法
這篇文章主要介紹了java自帶的MessageDigest實(shí)現(xiàn)文本的md5加密算法,需要的朋友可以參考下2015-12-12Java使用wait/notify實(shí)現(xiàn)線程間通信下篇
wait()和notify()是直接隸屬于Object類,也就是說所有對(duì)象都擁有這一對(duì)方法,下面這篇文章主要給大家介紹了關(guān)于使用wait/notify實(shí)現(xiàn)線程間通信的相關(guān)資料,需要的朋友可以參考下2022-12-12