使用Redis實(shí)現(xiàn)分布式鎖的代碼演示
一、引言
在分布式系統(tǒng)中,多個(gè)節(jié)點(diǎn)可能會(huì)同時(shí)訪問(wèn)共享資源,這就可能導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。為了解決這個(gè)問(wèn)題,分布式鎖應(yīng)運(yùn)而生。Redis作為一個(gè)高性能的內(nèi)存數(shù)據(jù)庫(kù),提供了多種機(jī)制來(lái)實(shí)現(xiàn)分布式鎖,本文將詳細(xì)介紹如何使用Redis實(shí)現(xiàn)分布式鎖。
二、分布式鎖的基本概念
分布式鎖是一種跨多個(gè)進(jìn)程或節(jié)點(diǎn)的鎖機(jī)制,用于協(xié)調(diào)對(duì)共享資源的訪問(wèn)。它保證了在任意時(shí)刻,只有一個(gè)客戶端能夠持有鎖,從而避免了并發(fā)訪問(wèn)導(dǎo)致的數(shù)據(jù)不一致問(wèn)題。
三、Redis實(shí)現(xiàn)分布式鎖的原理
Redis提供了多種命令和機(jī)制來(lái)實(shí)現(xiàn)分布式鎖,以下是幾種常見(jiàn)的實(shí)現(xiàn)方式:
1. SETNX命令
SETNX(SET if Not eXists)命令用于在鍵不存在時(shí)設(shè)置鍵的值。如果鍵已經(jīng)存在,則操作失敗。這個(gè)命令可以用來(lái)實(shí)現(xiàn)簡(jiǎn)單的分布式鎖。但是,SETNX命令本身并不具備設(shè)置過(guò)期時(shí)間的功能,因此需要結(jié)合EXPIRE命令一起使用。然而,這兩個(gè)命令并不是原子的,如果SETNX成功但EXPIRE失敗,就可能導(dǎo)致死鎖。
2. SET命令的擴(kuò)展參數(shù)
Redis的SET命令支持多個(gè)擴(kuò)展參數(shù),如NX(Not eXists)、EX(Expire seconds)和PX(Expire milliseconds)。其中,NX和EX/PX組合使用可以實(shí)現(xiàn)原子性的加鎖操作。例如,SET lock_key unique_lock_value NX EX 10命令表示在lock_key不存在時(shí)設(shè)置其值為unique_lock_value,并設(shè)置過(guò)期時(shí)間為10秒。
3. Lua腳本保證原子性
為了確保加鎖和釋放鎖的原子性,可以使用Lua腳本將多個(gè)Redis命令打包成一個(gè)原子操作。例如,可以使用Lua腳本來(lái)實(shí)現(xiàn)加鎖和設(shè)置過(guò)期時(shí)間的原子操作。
四、Redis實(shí)現(xiàn)分布式鎖的步驟
1. 引入Redis依賴
在Spring Boot項(xiàng)目中,可以通過(guò)在pom.xml文件中添加Redis的依賴來(lái)引入Redis支持。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>2. 加鎖實(shí)現(xiàn)
加鎖操作可以使用SET命令的擴(kuò)展參數(shù)或Lua腳本來(lái)實(shí)現(xiàn)。以下是一個(gè)使用SET命令的擴(kuò)展參數(shù)實(shí)現(xiàn)加鎖的示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 嘗試獲取分布式鎖
*
* @param lockKey 鎖鍵
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @param expireTime 過(guò)期時(shí)間
* @param timeUnit 時(shí)間單位
* @return 是否獲取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime, TimeUnit timeUnit) {
String result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, timeUnit);
return LOCK_SUCCESS.equals(result);
}
}3. 釋放鎖實(shí)現(xiàn)
釋放鎖操作需要確保只釋放自己持有的鎖,以防止誤刪其他客戶端的鎖。這可以通過(guò)先獲取鎖的值,然后判斷該值是否與自己的請(qǐng)求標(biāo)識(shí)一致來(lái)實(shí)現(xiàn)。為了確保操作的原子性,可以使用Lua腳本來(lái)實(shí)現(xiàn)。
import org.springframework.data.redis.core.script.DefaultRedisScript;
/**
* 釋放分布式鎖
*
* @param lockKey 鎖鍵
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @return 是否釋放成功
*/
public boolean unlock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
return redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId) == RELEASE_SUCCESS;
}4. 設(shè)置鎖過(guò)期時(shí)間
在加鎖時(shí),需要設(shè)置鎖的過(guò)期時(shí)間,以防止死鎖的發(fā)生。過(guò)期時(shí)間應(yīng)根據(jù)具體業(yè)務(wù)需求來(lái)設(shè)置,不宜過(guò)長(zhǎng)或過(guò)短。
五、代碼演示
1. 引入依賴
確保在pom.xml文件中引入了Redis的依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>2. 加鎖與釋放鎖的工具類
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 嘗試獲取分布式鎖
*
* @param lockKey 鎖鍵
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @param expireTime 過(guò)期時(shí)間
* @param timeUnit 時(shí)間單位
* @return 是否獲取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime, TimeUnit timeUnit) {
String result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, timeUnit);
return LOCK_SUCCESS.equals(result);
}
/**
* 釋放分布式鎖
*
* @param lockKey 鎖鍵
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @return 是否釋放成功
*/
public boolean unlock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
return redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId) == RELEASE_SUCCESS;
}
}3. 使用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LockController {
@Autowired
private RedisLockUtil redisLockUtil;
@GetMapping("/lock")
public String lock(@RequestParam String lockKey, @RequestParam long expireTime) {
String requestId = String.valueOf(System.currentTimeMillis());
if (redisLockUtil.tryLock(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS)) {
try {
// 執(zhí)行業(yè)務(wù)邏輯
return "加鎖成功,執(zhí)行業(yè)務(wù)邏輯";
} finally {
redisLockUtil.unlock(lockKey, requestId);
}
} else {
return "加鎖失敗,請(qǐng)重試";
}
}
}六、注意事項(xiàng)與優(yōu)化
1. 死鎖問(wèn)題
如果客戶端在持有鎖期間崩潰而沒(méi)有釋放鎖,就可能導(dǎo)致死鎖。為了避免這種情況,可以設(shè)置鎖的過(guò)期時(shí)間,當(dāng)鎖過(guò)期時(shí)自動(dòng)釋放。
2. 鎖競(jìng)爭(zhēng)與重試機(jī)制
在高并發(fā)環(huán)境下,多個(gè)客戶端可能會(huì)同時(shí)嘗試獲取同一個(gè)鎖,這就可能導(dǎo)致鎖競(jìng)爭(zhēng)。為了
到此這篇關(guān)于使用Redis實(shí)現(xiàn)分布式鎖的技術(shù)詳解的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Redis分布式鎖的幾種實(shí)現(xiàn)方法
- Redis使用SETNX命令實(shí)現(xiàn)分布式鎖
- Redis分布式鎖使用及說(shuō)明
- Redisson分布式鎖解鎖異常問(wèn)題
- redis分布式鎖實(shí)現(xiàn)示例
- Redis 實(shí)現(xiàn)分布式鎖時(shí)需要考慮的問(wèn)題解決方案
- Redis實(shí)現(xiàn)分布式鎖的示例代碼
- Redission實(shí)現(xiàn)分布式鎖lock()和tryLock()方法的區(qū)別小結(jié)
- 從原理到實(shí)踐分析?Redis?分布式鎖的多種實(shí)現(xiàn)方案
- Redis本地鎖和分布式鎖的區(qū)別小結(jié)
相關(guān)文章
使用redis分布式鎖解決并發(fā)線程資源共享問(wèn)題
這篇文章主要介紹了使用redis分布式鎖解決并發(fā)線程資源共享問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
redis 數(shù)據(jù)刪除策略和逐出算法的問(wèn)題小結(jié)
這篇文章主要介紹了redis 數(shù)據(jù)刪除策略和逐出算法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Redis實(shí)現(xiàn)Session共享與單點(diǎn)登錄
本文主要介紹了Redis實(shí)現(xiàn)Session共享與單點(diǎn)登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
redis中hiredis-API函數(shù)的調(diào)用方法
這篇文章主要介紹了redis中hiredis-API函數(shù)的調(diào)用,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-09-09

