聊聊使用RedisTemplat實(shí)現(xiàn)簡(jiǎ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:使用占位符加鎖:
占位符加鎖問題
:出現(xiàn)異常時(shí)無法釋放鎖,導(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)異常無法釋放鎖:
之后所有線程都無法訪問:
解決方案
:為鎖加一個(gè)有效時(shí)間。
1.2:使用占位符設(shè)置有效時(shí)間解決死鎖問題:
占位符設(shè)置有效時(shí)間問題
:即使某線程出現(xiàn)異常,但占位符過了有效時(shí)間,鎖就會(huì)釋放。但是在大量線程同時(shí)訪問時(shí),如果線程1被外界因素影響(網(wǎng)絡(luò)波動(dòng),服務(wù)器出問題等等),線程1的業(yè)務(wù)還沒完成,但鎖的有效時(shí)間到了的話,下一個(gè)線程就會(huì)進(jìn)來,就會(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腳本解決線程不安全問題:
lua腳本可以寫在Redis服務(wù)器上:
優(yōu)點(diǎn)
: 在服務(wù)器上運(yùn)行速度快
缺點(diǎn)
: 修改代碼時(shí)比較麻煩
lua腳本可以通過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)文章
從一個(gè)小需求感受Redis的獨(dú)特魅力(需求設(shè)計(jì))
Redis在實(shí)際應(yīng)用中使用的非常廣泛,本篇文章就從一個(gè)簡(jiǎn)單的需求說起,為你講述一個(gè)需求是如何從頭到尾開始做的,又是如何一步步完善的2019-12-12Redis 操作多個(gè)數(shù)據(jù)庫(kù)的配置的方法實(shí)現(xiàn)
本文主要介紹了Redis 操作多個(gè)數(shù)據(jù)庫(kù)的配置的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03利用redis實(shí)現(xiàn)分布式鎖,快速解決高并發(fā)時(shí)的線程安全問題
這篇文章主要介紹了利用redis實(shí)現(xiàn)分布式鎖,快速解決高并發(fā)時(shí)的線程安全問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01將音頻文件轉(zhuǎn)二進(jìn)制分包存儲(chǔ)到Redis的實(shí)現(xiàn)方法(奇淫技巧操作)
這篇文章主要介紹了將音頻文件轉(zhuǎn)二進(jìn)制分包存儲(chǔ)到Redis的實(shí)現(xiàn)方法(奇淫技巧操作),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07