SpringBoot使用Redis實(shí)現(xiàn)分布式鎖
前言
在單機(jī)應(yīng)用時(shí)代,我們對(duì)一個(gè)共享的對(duì)象進(jìn)行多線程訪問(wèn)的時(shí)候,使用java的synchronized關(guān)鍵字或者ReentrantLock類(lèi)對(duì)操作的對(duì)象加鎖就可以解決對(duì)象的線程安全問(wèn)題。
分布式應(yīng)用時(shí)代這個(gè)方法卻行不通了,我們的應(yīng)用可能被部署到多臺(tái)機(jī)器上,運(yùn)行在不同的JVM里,一個(gè)對(duì)象可能同時(shí)存在多臺(tái)機(jī)器的內(nèi)存中,怎樣使共享對(duì)象同時(shí)只被一個(gè)線程處理就成了一個(gè)問(wèn)題。
在分布式系統(tǒng)中為了保證一個(gè)對(duì)象在高并發(fā)的情況下只能被一個(gè)線程使用,我們需要一種跨JVM的互斥機(jī)制來(lái)控制共享資源的訪問(wèn),此時(shí)就需要用到我們的分布式鎖了。
分布式鎖一般有三種實(shí)現(xiàn)方式:1.通過(guò)數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖;2.通過(guò)緩存(Redis等)實(shí)現(xiàn)分布式鎖;3.通過(guò)Zookeeper實(shí)現(xiàn)分布式鎖。本篇文章主要介紹第二種通過(guò)Redis實(shí)現(xiàn)分布式鎖的方式。
分布式鎖的需要具備的條件
為了保證分布式鎖的可用性,需要具備一下五點(diǎn)條件:
1、在同一時(shí)間保證只有一臺(tái)機(jī)器的一個(gè)線程可以持有鎖。
2、不能發(fā)生死鎖,無(wú)論何時(shí)持有鎖的機(jī)器崩潰掛掉了都要能自動(dòng)釋放鎖。
3、高效的獲取和釋放鎖。
4、具備非阻塞性,一旦獲取不到鎖就立刻返回加鎖失敗。
5、獨(dú)占性,即自己加的鎖只有自己才能釋放。
代碼實(shí)現(xiàn)
組件依賴(lài)
首先在pom.xml文件中添加依賴(lài):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
加鎖代碼
代碼如下:
/** * 獲取鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @param expireTime 鎖的過(guò)期時(shí)間(單位:秒) * @return */ public boolean lock(String lockKey, String identity, long expireTime){ boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS); return opsForValue; }
加鎖的方法只需要三個(gè)參數(shù):lockKey、identity、expireTime。
- 第一個(gè)參數(shù)lockKey為key,一個(gè)資源對(duì)應(yīng)一個(gè)唯一的key。
- 第二個(gè)參數(shù)identity為身份標(biāo)識(shí),作為此key對(duì)應(yīng)的value存儲(chǔ),為了判斷在釋放鎖時(shí)是不是和加鎖的身份相同,防止別人釋放鎖。
- 第三個(gè)參數(shù)expireTime為過(guò)期時(shí)間,此參數(shù)保證程序加鎖后崩潰導(dǎo)致不能主動(dòng)釋放鎖的時(shí)候自動(dòng)釋放鎖,防止出現(xiàn)死鎖。
為什么使用setIfAbsent方法呢?這個(gè)方法的好處就是,如果redis中已經(jīng)存在這個(gè)key了,就會(huì)返回失敗,并且不改變r(jià)edis中的數(shù)據(jù),這樣就不會(huì)把別的線程的加的鎖給覆蓋掉。
解鎖代碼
代碼如下:
/** * 釋放鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @return */ public boolean releaseLock(String lockKey, String identity){ String luaScript = "if " + " redis.call('get', KEYS[1]) == ARGV[1] " + "then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List<String> keys = new ArrayList<>(); keys.add(lockKey); boolean result = redisTemplate.execute(redisScript, keys, identity); return result; }
解鎖的方法只需兩個(gè)參數(shù):lockKey、identity。
- 第一個(gè)參數(shù)lockKey為key,一個(gè)資源對(duì)應(yīng)一個(gè)唯一的key。
- 第二個(gè)參數(shù)identity為身份標(biāo)識(shí),作為此key對(duì)應(yīng)的value存儲(chǔ),為了判斷在釋放鎖時(shí)是不是和加鎖的身份相同,防止別人釋放鎖。
此處使用Lua腳本來(lái)判斷身份,身份相同就刪除,身份不同就不對(duì)數(shù)據(jù)做操作并返回失敗。為什么要使用Lua腳本呢?這是為了要保證操作的原子性,redis在執(zhí)行Lua腳本的時(shí)候是把腳本當(dāng)作一個(gè)命令來(lái)執(zhí)行的,我們都知道redis的命令是都是原子操作,這樣就保證了操作的原子性。
測(cè)試代碼
package com.qixi.lock.demo.lockdemo.controller; import com.qixi.lock.demo.lockdemo.util.RedisLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 測(cè)試分布式鎖 * @author ZhengNC * @date 2020/5/13 17:27 */ @RestController @RequestMapping("test") public class TestRedisLockController { private final String lockKeyName = "testKey"; @Autowired private RedisLock redisLock; /** * 測(cè)試加鎖 * @param id 加鎖的資源id * @param identity 身份標(biāo)識(shí) * @return */ @GetMapping("lock") public String lock(@RequestParam("id") String id, @RequestParam("identity") String identity){ String lockKey = lockKeyName+":"+id; boolean lockSuccess = redisLock.lock(lockKey, identity, 60); String result = "lock failed"; if (lockSuccess){ result = "lock success"; } return result; } /** * 測(cè)試釋放鎖 * @param id 釋放鎖的資源id * @param identity 身份標(biāo)識(shí) * @return */ @GetMapping("release") public String release(@RequestParam("id") String id, @RequestParam("identity") String identity){ String lockKey = lockKeyName+":"+id; boolean releaseSuccess = redisLock.releaseLock(lockKey, identity); String result = "release failed"; if (releaseSuccess){ result = "release success"; } return result; } }
package com.qixi.lock.demo.lockdemo.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * 分布式鎖Redis工具類(lèi) * @author ZhengNC * @date 2020/5/13 17:27 */ @Component public class RedisLock { @Autowired private RedisTemplate<String, String> redisTemplate; /** * 獲取鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @param expireTime 鎖的過(guò)期時(shí)間(單位:秒) * @return */ public boolean lock(String lockKey, String identity, long expireTime){ boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS); return lockResult; } /** * 釋放鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @return */ public boolean releaseLock(String lockKey, String identity){ String luaScript = "if " + " redis.call('get', KEYS[1]) == ARGV[1] " + "then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List<String> keys = new ArrayList<>(); keys.add(lockKey); boolean result = redisTemplate.execute(redisScript, keys, identity); return result; } }
結(jié)語(yǔ)
感謝大家閱讀我的文章,更歡迎大家指出我的問(wèn)題,希望能在這里通過(guò)討論取得共同的進(jìn)步。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot利用注解來(lái)實(shí)現(xiàn)Redis分布式鎖
- Spring?Boot?集成Redisson實(shí)現(xiàn)分布式鎖詳細(xì)案例
- springboot 集成redission 以及分布式鎖的使用詳解
- SpringBoot之使用Redis實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- Redis分布式鎖升級(jí)版RedLock及SpringBoot實(shí)現(xiàn)方法
- SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼
- SpringBoot使用Redisson實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的方法示例
- springboot+redis分布式鎖實(shí)現(xiàn)模擬搶單
- Spring?Boot?3.0x的Redis?分布式鎖的概念和原理
相關(guān)文章
MyBatis注解方式之@Update/@Delete使用詳解
這篇文章主要介紹了MyBatis注解方式之@Update/@Delete使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11java設(shè)計(jì)模式之單例模式的詳解及優(yōu)點(diǎn)
這篇文章主要介紹了java設(shè)計(jì)模式之單例模式的詳解及優(yōu)點(diǎn)的相關(guān)資料,如果一個(gè)類(lèi)始終只能創(chuàng)建一個(gè)實(shí)例,那么這個(gè)類(lèi)被稱(chēng)為單例類(lèi),這種設(shè)計(jì)模式被稱(chēng)為單例模式,需要的朋友可以參考下2017-08-08Java數(shù)組集合的深度復(fù)制代碼實(shí)例
這篇文章主要介紹了Java數(shù)組集合的深度復(fù)制代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11快速搭建一個(gè)SpringBoot項(xiàng)目(純小白搭建教程)
本文主要介紹了快速搭建一個(gè)SpringBoot項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11java 畫(huà)pdf用itext調(diào)整表格寬度、自定義各個(gè)列寬的方法
這篇文章主要介紹了java 畫(huà)pdf用itext調(diào)整表格寬度、自定義各個(gè)列寬的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01SpringCloud實(shí)現(xiàn)文件上傳功能的方法詳解
這篇文章主要為大家詳細(xì)介紹了SpringCloud如何實(shí)現(xiàn)文件上傳功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定的借鑒價(jià)值,需要的可以參考一下2022-08-08