聊聊使用RedisTemplat實(shí)現(xiàn)簡(jiǎn)單的分布式鎖的問(wèn)題
不使用redisson框架實(shí)現(xiàn)Redis分布式鎖
準(zhǔn)備工作:
導(dǎo)入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
編寫RedisConfig類
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String , Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//String類型 key序列器
redisTemplate.setKeySerializer(new StringRedisSerializer());
//String類型 value序列器
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//Hash類型 key序列器
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//Hash類型 value序列器
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//將連接工廠注入
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
1.在SpringBootTest中編寫測(cè)試模塊
1.1:使用占位符加鎖:
占位符加鎖問(wèn)題:出現(xiàn)異常時(shí)無(wú)法釋放鎖,導(dǎo)致后繼進(jìn)入的線程成為死鎖
@SpringBootTest
class ApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void lodsTest01(){
ValueOperations valueOperations = redisTemplate.opsForValue();
//創(chuàng)建一個(gè)占位符,如果key不存在才可以設(shè)置成功
Boolean isLock = valueOperations.setIfAbsent("k1", "v1");
//如果占位成功,進(jìn)行正常操作
if (isLock){
//設(shè)置一個(gè)name存到redis
valueOperations.set("name","xxxx");
//從redis取出name
String name = (String) valueOperations.get("name");
System.out.println("name = " + name);
//手動(dòng)制造異常
Integer.parseInt("xxxx");
//操作結(jié)束刪除鎖
redisTemplate.delete("k1");
}else{
System.out.println("有線程在用,請(qǐng)稍后在試");
}
}
}
測(cè)試
第一個(gè)線程出現(xiàn)異常無(wú)法釋放鎖:

之后所有線程都無(wú)法訪問(wèn):

解決方案:為鎖加一個(gè)有效時(shí)間。
1.2:使用占位符設(shè)置有效時(shí)間解決死鎖問(wèn)題:
占位符設(shè)置有效時(shí)間問(wèn)題:即使某線程出現(xiàn)異常,但占位符過(guò)了有效時(shí)間,鎖就會(huì)釋放。但是在大量線程同時(shí)訪問(wèn)時(shí),如果線程1被外界因素影響(網(wǎng)絡(luò)波動(dòng),服務(wù)器出問(wèn)題等等),線程1的業(yè)務(wù)還沒(méi)完成,但鎖的有效時(shí)間到了的話,下一個(gè)線程就會(huì)進(jìn)來(lái),就會(huì)出現(xiàn)線程不安全的情況,出現(xiàn)線程互相刪鎖的情況。
@Test
public void testLock02() {
ValueOperations valueOperations = redisTemplate.opsForValue();
//如果key不存在才可以設(shè)置成功,設(shè)置一個(gè)有效時(shí)間防止線程異常出現(xiàn)死鎖
Boolean isLock = valueOperations.setIfAbsent("k1", "v1",5, TimeUnit.SECONDS);
//如果占位成功,進(jìn)行正常操作
if (isLock){
//設(shè)置一個(gè)name存到redis
valueOperations.set("name","xxxx");
//從redis取出name
String str = (String) valueOperations.get("name");
System.out.println("name = " + str);
//制造異常
Integer.parseInt("xxxx");
//操作結(jié)束刪除鎖
redisTemplate.delete("k1");
}else{
System.out.println("有線程在用,請(qǐng)稍后在試");
}
}
解決方案: 使用lua腳本,給每個(gè)鎖的key對(duì)應(yīng)的value設(shè)置一個(gè)隨機(jī)數(shù)
1.3:使用lua腳本解決線程不安全問(wèn)題:
lua腳本可以寫在Redis服務(wù)器上:
優(yōu)點(diǎn): 在服務(wù)器上運(yùn)行速度快
缺點(diǎn): 修改代碼時(shí)比較麻煩
lua腳本可以通過(guò)java發(fā)送
優(yōu)點(diǎn): 修改代碼方便
缺點(diǎn): 每次發(fā)送請(qǐng)求時(shí)都需要占用網(wǎng)絡(luò)資源
1.3.1:編寫lua腳本

if redis.call("get",KEYS[1])==ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
1.3.2:修改ReidsConfig類
@Bean
public DefaultRedisScript<Boolean> defaultRedisScript(){
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
//lock.lua腳本位置和application.yml同級(jí)目錄
redisScript.setLocation(new ClassPathResource("lock.lua"));
//設(shè)置類型為boolean
redisScript.setResultType(Boolean.class);
return redisScript;
}
1.3.3:編寫測(cè)試模塊
@Test
public void testLock03(){
ValueOperations valueOperations = redisTemplate.opsForValue();
String value = UUID.randomUUID().toString();
//如果key不存在才可以設(shè)置成功,設(shè)置一個(gè)value為隨機(jī)數(shù)的值,防止出現(xiàn)線程太多 導(dǎo)致線程不安全
Boolean isLock = valueOperations.setIfAbsent("k1", value, 5, TimeUnit.SECONDS);
//如果占位成功,進(jìn)行正常操作
if (isLock){
//設(shè)置一個(gè)name存到redis
valueOperations.set("name","xxxx");
//從redis取出name
String name = (String) valueOperations.get("name");
System.out.println("name = " + name);
//為redis發(fā)送lua腳本刪除鎖對(duì)應(yīng)的value
Boolean aBoolean = (Boolean) redisTemplate.execute(redisScript, Collections.singletonList("k1"), value);
System.out.println(aBoolean);
}else{
System.out.println("有線程在用,請(qǐng)稍后在試");
}
}
測(cè)試結(jié)果:
順利把name值存到redis中并把鎖刪除并返回true

鎖會(huì)被正常刪除只留下name:

到此這篇關(guān)于使用RedisTemplat實(shí)現(xiàn)簡(jiǎn)單的分布式鎖的文章就介紹到這了,更多相關(guān)RedisTemplat分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis報(bào)錯(cuò):無(wú)法連接Redis服務(wù)的解決方法
在Linux系統(tǒng)上運(yùn)行Redis服務(wù)時(shí),有時(shí)會(huì)遇到“無(wú)法連接Redis服務(wù)”的報(bào)錯(cuò),本文就詳細(xì)的介紹一下解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
linux 常見(jiàn)的標(biāo)識(shí)與Redis數(shù)據(jù)庫(kù)詳解
這篇文章主要介紹了linux 常見(jiàn)的標(biāo)識(shí)與Redis數(shù)據(jù)庫(kù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
Redis設(shè)置Hash數(shù)據(jù)類型的過(guò)期時(shí)間
在Redis中,我們可以使用Hash數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)一組鍵值對(duì),而有時(shí)候,我們可能需要設(shè)置這些鍵值對(duì)的過(guò)期時(shí)間,本文主要介紹了Redis設(shè)置Hash數(shù)據(jù)類型的過(guò)期時(shí)間,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Redis中的String類型及使用Redis解決訂單秒殺超賣問(wèn)題
這篇文章主要介紹了Redis中的String類型及使用Redis解決訂單秒殺超賣問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
這篇文章主要介紹了SpringBoot?+?Redis?實(shí)現(xiàn)接口限流,Redis?除了做緩存,還能干很多很多事情:分布式鎖、限流、處理請(qǐng)求接口冪等,文中給大家提到了限流注解的創(chuàng)建方式,需要的朋友可以參考下2022-05-05

