redis深入淺出分布式鎖實(shí)現(xiàn)下篇
優(yōu)化之UUID防誤刪
問(wèn)題:刪除操作缺乏原子性。
場(chǎng)景:
index1執(zhí)行刪除時(shí),查詢到的lock值確實(shí)和uuid相等
uuid=v1
set(lock,uuid);
index1執(zhí)行刪除前,lock剛好過(guò)期時(shí)間已到,被redis自動(dòng)釋放,在redis中沒有了lock,沒有了鎖。
index2獲取了lock
index2線程獲取到了cpu的資源,開始執(zhí)行方法
uuid=v2
set(lock,uuid);
index1執(zhí)行刪除,此時(shí)會(huì)把index2的lock刪除
index1 因?yàn)橐呀?jīng)在方法中了,所以不需要重新上鎖。index1有執(zhí)行的權(quán)限。index1已經(jīng)比較完成了,這個(gè)時(shí)候,開始執(zhí)行
刪除的index2的鎖!
優(yōu)化之LUA腳本保證刪除的原子性
@GetMapping("testLockLua") public void testLockLua() { //1 聲明一個(gè)uuid ,將做為一個(gè)value 放入我們的key所對(duì)應(yīng)的值中 String uuid = UUID.randomUUID().toString(); //2 定義一個(gè)鎖:lua 腳本可以使用同一把鎖,來(lái)實(shí)現(xiàn)刪除! String skuId = "25"; // 訪問(wèn)skuId 為25號(hào)的商品 100008348542 String locKey = "lock:" + skuId; // 鎖住的是每個(gè)商品的數(shù)據(jù) // 3 獲取鎖 Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS); // 第一種: lock 與過(guò)期時(shí)間中間不寫任何的代碼。 // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//設(shè)置過(guò)期時(shí)間 // 如果true if (lock) { // 執(zhí)行的業(yè)務(wù)邏輯開始 // 獲取緩存中的num 數(shù)據(jù) Object value = redisTemplate.opsForValue().get("num"); // 如果是空直接返回 if (StringUtils.isEmpty(value)) { return; } // 不是空 如果說(shuō)在這出現(xiàn)了異常! 那么delete 就刪除失??! 也就是說(shuō)鎖永遠(yuǎn)存在! int num = Integer.parseInt(value + ""); // 使num 每次+1 放入緩存 redisTemplate.opsForValue().set("num", String.valueOf(++num)); /*使用lua腳本來(lái)鎖*/ // 定義lua 腳本 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; // 使用redis執(zhí)行l(wèi)ua執(zhí)行 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(script); // 設(shè)置一下返回值類型 為L(zhǎng)ong // 因?yàn)閯h除判斷的時(shí)候,返回的0,給其封裝為數(shù)據(jù)類型。如果不封裝那么默認(rèn)返回String 類型, // 那么返回字符串與0 會(huì)有發(fā)生錯(cuò)誤。 redisScript.setResultType(Long.class); // 第一個(gè)要是script 腳本 ,第二個(gè)需要判斷的key,第三個(gè)就是key所對(duì)應(yīng)的值。 redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid); } else { // 其他線程等待 try { // 睡眠 Thread.sleep(1000); // 睡醒了之后,調(diào)用方法。 testLockLua(); } catch (InterruptedException e) { e.printStackTrace(); } } }
Lua 腳本詳解:
項(xiàng)目中正確使用
定義key,key應(yīng)該是為每個(gè)sku定義的,也就是每個(gè)sku有一把鎖。
String locKey ="lock:"+skuId; // 鎖住的是每個(gè)商品的數(shù)據(jù) Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid,3,TimeUnit.SECONDS);
總結(jié)
加鎖
使用lua釋放鎖
重試
為了確保分布式鎖可用,我們至少要確保鎖的實(shí)現(xiàn)同時(shí)滿足以下四個(gè)條件:
- 互斥性。在任意時(shí)刻,只有一個(gè)客戶端能持有鎖。
- 不會(huì)發(fā)生死鎖。即使有一個(gè)客戶端在持有鎖的期間崩潰而沒有主動(dòng)解鎖,也能保證后續(xù)其他客戶端能加鎖。
- 解鈴還須系鈴人。加鎖和解鎖必須是同一個(gè)客戶端,客戶端自己不能把別人加的鎖給解了。
- 加鎖和解鎖必須具有原子性
到此這篇關(guān)于redis深入淺出分布式鎖實(shí)現(xiàn)下篇的文章就介紹到這了,更多相關(guān)redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JWT 設(shè)置token過(guò)期時(shí)間無(wú)效的解決
這篇文章主要介紹了JWT 設(shè)置token過(guò)期時(shí)間無(wú)效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Servlet實(shí)現(xiàn)點(diǎn)擊計(jì)數(shù)器的方法
這篇文章主要介紹了Servlet實(shí)現(xiàn)點(diǎn)擊計(jì)數(shù)器的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08Spring mvc如何實(shí)現(xiàn)數(shù)據(jù)處理
這篇文章主要介紹了Spring mvc如何實(shí)現(xiàn)數(shù)據(jù)處理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03詳解Maven 搭建spring boot多模塊項(xiàng)目(附源碼)
這篇文章主要介紹了詳解Maven 搭建spring boot多模塊項(xiàng)目(附源碼),具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09springcloud安裝rabbitmq并配置延遲隊(duì)列插件的過(guò)程詳解
本期主要講解如何利用docker快速安裝rabbitmq并且配置延遲隊(duì)列插件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05Java 線程狀態(tài)和等待喚醒機(jī)制和線程池的實(shí)現(xiàn)
這篇文章主要介紹了Java 線程狀態(tài)和等待喚醒機(jī)制和線程池的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Mybatis日志參數(shù)快速替換占位符工具的詳細(xì)步驟
這篇文章主要介紹了Mybatis日志參數(shù)快速替換占位符工具的詳細(xì)步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08