Redis如何實(shí)現(xiàn)分布式鎖詳解
一、前言
在Java的并發(fā)編程中,我們通過(guò)鎖,來(lái)避免由于競(jìng)爭(zhēng)而造成的數(shù)據(jù)不一致問(wèn)題。通常,我們以synchronized 、Lock來(lái)使用它。
但是Java中的鎖,只能保證在同一個(gè)JVM進(jìn)程內(nèi)中執(zhí)行。如果在分布式集群環(huán)境下,就需要分布式鎖了。
通常的分布式鎖的實(shí)現(xiàn)方式有redis,zookeeper,但是一般我們的程序中都會(huì)用到redis,用redis做分布式鎖,也能夠降低成本。
二、實(shí)現(xiàn)原理
2.1 加鎖
加鎖實(shí)際上就是在redis中,給Key鍵設(shè)置一個(gè)值,為避免死鎖,并給定一個(gè)過(guò)期時(shí)間。
在Redis 2.6.12以及之前,可以通過(guò)setnx key value (key不存在才設(shè)置成功)設(shè)置值,通過(guò)expire key seconds設(shè)置過(guò)期時(shí)間。但是由于是兩個(gè)命令,不是原子的。如果在設(shè)置值之后還沒(méi)有來(lái)得及設(shè)置過(guò)期時(shí)間,程序掛掉了,那么這個(gè)key就永遠(yuǎn)的存在redis中了。
在Redis 2.6.12之后,redis提供了set key value EX seconds NX 和set key value PX millisecond NX 來(lái)原子性的設(shè)置值和設(shè)置過(guò)期時(shí)間。
2.2 解鎖
解鎖的過(guò)程就是將Key鍵刪除。但也不能亂刪,不能說(shuō)客戶(hù)端1的請(qǐng)求將客戶(hù)端2的鎖給刪除掉,在刪除前需要判斷是不是設(shè)置的value,如果是才刪除。
為了保證解鎖操作的原子性,我們用LUA腳本完成這一操作。先判斷當(dāng)前鎖的字符串是否與傳入的值相等,是的話就刪除Key,解鎖成功。LUA腳本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
三、通過(guò)RedisTemplate實(shí)現(xiàn)分布式鎖
我們通過(guò)SpringBoot構(gòu)建的應(yīng)用程序一般都是用RedisTemplate來(lái)操作緩存redis。下面介紹基于RedisTemplate來(lái)實(shí)現(xiàn)分布式鎖。
import lombok.extern.slf4j.Slf4j;
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.Service;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class RedisLockUtil {
@Autowired
private RedisTemplate redisTemplate;
//獲取鎖超時(shí)時(shí)間
private long timeout = 60000;
/**
* 獲取鎖
* @param key
* @param value
* @param ms
* @return
*/
public Boolean getLock(String key, String value, Long ms) {
long startTime = System.currentTimeMillis();
while (true) {
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, ms, TimeUnit.MILLISECONDS);
if (flag) {
return true;
}
//避免一直無(wú)限獲取鎖
if (System.currentTimeMillis() - startTime > timeout) {
return false;
}
try {
log.info("{}重試鎖", key);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 解鎖
* @param key
* @param value
* @return
*/
public Boolean unLock(String key, String value) {
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del',KEYS[1]) " +
"else" +
" return 0 " +
"end";
// 構(gòu)造RedisScript并指定返回類(lèi)類(lèi)型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
// 參數(shù)一:redisScript,參數(shù)二:key列表,參數(shù)三:arg(可多個(gè))
Object result = redisTemplate.execute(redisScript, Arrays.asList(key), value);
return "1".equals(result.toString());
}
}
四、通過(guò)Redisson實(shí)現(xiàn)
Redisson為我們封裝了細(xì)節(jié),可以開(kāi)箱即用。
引入maven依賴(lài):
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.14.0</version> </dependency>
配置類(lèi):
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* redisson 配置類(lèi)
*/
@Configuration
@Data
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient getRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
//添加主從配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
}
redis屬性配置:
spring:
redis:
host: 127.0.0.1
port: 6379
password: root
封裝的加鎖解鎖工具類(lèi):
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* redis分布式鎖幫助類(lèi)
*/
@Component
public class RedissLockUtil {
@Autowired
private RedissonClient redissonClient;
/**
* 加鎖
* @param lockKey
* @return
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* 釋放鎖
* @param lockKey
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
/**
* 釋放鎖
* @param lock
*/
public void unlock(RLock lock) {
lock.unlock();
}
/**
* 帶超時(shí)的鎖
* @param lockKey
* @param timeout 超時(shí)時(shí)間 單位:秒
*/
public RLock lock(String lockKey, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.SECONDS);
return lock;
}
/**
* 帶超時(shí)的鎖
* @param lockKey
* @param unit 時(shí)間單位
* @param timeout 超時(shí)時(shí)間
*/
public RLock lock(String lockKey, TimeUnit unit , int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param waitTime 最多等待時(shí)間
* @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間
* @return
*/
public boolean tryLock(String lockKey, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param unit 時(shí)間單位
* @param waitTime 最多等待時(shí)間
* @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間
* @return
*/
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
}
到此這篇關(guān)于Redis如何實(shí)現(xiàn)分布式鎖詳解的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決mybatis映射mapper.xml文件不編譯的問(wèn)題
這篇文章主要介紹了解決mybatis映射mapper.xml文件不編譯的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
SpringBoot實(shí)現(xiàn)文件斷點(diǎn)續(xù)傳功能詳解
在處理大文件傳輸或網(wǎng)絡(luò)不穩(wěn)定的情況下,文件斷點(diǎn)續(xù)傳功能顯得尤為重要,本文將詳細(xì)介紹如何使用Spring Boot實(shí)現(xiàn)文件的斷點(diǎn)續(xù)傳功能,需要的可以了解下2025-04-04
Reactor定制一個(gè)生產(chǎn)的WebClient實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Reactor定制一個(gè)生產(chǎn)的WebClient實(shí)現(xiàn)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例
這篇文章主要介紹了SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
SpringBoot之@Controller和@RequestMapping的實(shí)現(xiàn)原理解讀
這篇文章主要介紹了SpringBoot之@Controller和@RequestMapping的實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
使用Mybatis實(shí)現(xiàn)分頁(yè)效果示例
大家好,本篇文章主要講的是使用Mybatis實(shí)現(xiàn)分頁(yè)效果示例,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
java基于servlet實(shí)現(xiàn)文件上傳功能
這篇文章主要為大家詳細(xì)介紹了java基于servlet實(shí)現(xiàn)文件上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問(wèn)題
這篇文章主要介紹了解決spring中redistemplate不能用通配符keys查出相應(yīng)Key的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11

