這篇文章主要講解了SpringBoot怎么使用redis實(shí)現(xiàn)分布式鎖,內(nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。
福建網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)于2013年成立到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
前言
在單機(jī)應(yīng)用時代,我們對一個共享的對象進(jìn)行多線程訪問的時候,使用java的synchronized關(guān)鍵字或者ReentrantLock類對操作的對象加鎖就可以解決對象的線程安全問題。
分布式應(yīng)用時代這個方法卻行不通了,我們的應(yīng)用可能被部署到多臺機(jī)器上,運(yùn)行在不同的JVM里,一個對象可能同時存在多臺機(jī)器的內(nèi)存中,怎樣使共享對象同時只被一個線程處理就成了一個問題。
在分布式系統(tǒng)中為了保證一個對象在高并發(fā)的情況下只能被一個線程使用,我們需要一種跨JVM的互斥機(jī)制來控制共享資源的訪問,此時就需要用到我們的分布式鎖了。
分布式鎖一般有三種實(shí)現(xiàn)方式:1.通過數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖;2.通過緩存(Redis等)實(shí)現(xiàn)分布式鎖;3.通過Zookeeper實(shí)現(xiàn)分布式鎖。本篇文章主要介紹第二種通過Redis實(shí)現(xiàn)分布式鎖的方式。
分布式鎖的需要具備的條件
為了保證分布式鎖的可用性,需要具備一下五點(diǎn)條件:
1、在同一時間保證只有一臺機(jī)器的一個線程可以持有鎖。
2、不能發(fā)生死鎖,無論何時持有鎖的機(jī)器崩潰掛掉了都要能自動釋放鎖。
3、高效的獲取和釋放鎖。
4、具備非阻塞性,一旦獲取不到鎖就立刻返回加鎖失敗。
5、獨(dú)占性,即自己加的鎖只有自己才能釋放。
代碼實(shí)現(xiàn)
組件依賴
首先在pom.xml文件中添加依賴:
org.springframework.boot spring-boot-starter-data-redis
加鎖代碼
代碼如下:
/** * 獲取鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(保證鎖不會被其他人釋放) * @param expireTime 鎖的過期時間(單位:秒) * @return */ public boolean lock(String lockKey, String identity, long expireTime){ boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS); return opsForValue; }
加鎖的方法只需要三個參數(shù):lockKey、identity、expireTime。
為什么使用setIfAbsent方法呢?這個方法的好處就是,如果redis中已經(jīng)存在這個key了,就會返回失敗,并且不改變redis中的數(shù)據(jù),這樣就不會把別的線程的加的鎖給覆蓋掉。
解鎖代碼
代碼如下:
/** * 釋放鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(保證鎖不會被其他人釋放) * @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"; DefaultRedisScriptredisScript = new DefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys = new ArrayList<>(); keys.add(lockKey); boolean result = redisTemplate.execute(redisScript, keys, identity); return result; }
解鎖的方法只需兩個參數(shù):lockKey、identity。
此處使用Lua腳本來判斷身份,身份相同就刪除,身份不同就不對數(shù)據(jù)做操作并返回失敗。為什么要使用Lua腳本呢?這是為了要保證操作的原子性,redis在執(zhí)行Lua腳本的時候是把腳本當(dāng)作一個命令來執(zhí)行的,我們都知道redis的命令是都是原子操作,這樣就保證了操作的原子性。
測試代碼
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; /** * 測試分布式鎖 * @author ZhengNC * @date 2020/5/13 17:27 */ @RestController @RequestMapping("test") public class TestRedisLockController { private final String lockKeyName = "testKey"; @Autowired private RedisLock redisLock; /** * 測試加鎖 * @param id 加鎖的資源id * @param identity 身份標(biāo)識 * @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; } /** * 測試釋放鎖 * @param id 釋放鎖的資源id * @param identity 身份標(biāo)識 * @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工具類 * @author ZhengNC * @date 2020/5/13 17:27 */ @Component public class RedisLock { @Autowired private RedisTemplateredisTemplate; /** * 獲取鎖 * @param lockKey 鎖 * @param identity 身份標(biāo)識(保證鎖不會被其他人釋放) * @param expireTime 鎖的過期時間(單位:秒) * @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)識(保證鎖不會被其他人釋放) * @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 redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys = new ArrayList<>(); keys.add(lockKey); boolean result = redisTemplate.execute(redisScript, keys, identity); return result; } }
看完上述內(nèi)容,是不是對SpringBoot怎么使用Redis實(shí)現(xiàn)分布式鎖有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。