聊聊使用RedisTemplat實現簡單的分佈式鎖的問題
不使用redisson框架實現Redis分佈式鎖
準備工作:
導入依賴
<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中編寫測試模塊
1.1:使用占位符加鎖:
占位符加鎖問題
:出現異常時無法釋放鎖,導致後繼進入的線程成為死鎖
@SpringBootTest class ApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test public void lodsTest01(){ ValueOperations valueOperations = redisTemplate.opsForValue(); //創建一個占位符,如果key不存在才可以設置成功 Boolean isLock = valueOperations.setIfAbsent("k1", "v1"); //如果占位成功,進行正常操作 if (isLock){ //設置一個name存到redis valueOperations.set("name","xxxx"); //從redis取出name String name = (String) valueOperations.get("name"); System.out.println("name = " + name); //手動制造異常 Integer.parseInt("xxxx"); //操作結束刪除鎖 redisTemplate.delete("k1"); }else{ System.out.println("有線程在用,請稍後在試"); } } }
測試
第一個線程出現異常無法釋放鎖:
之後所有線程都無法訪問:
解決方案
:為鎖加一個有效時間。
1.2:使用占位符設置有效時間解決死鎖問題:
占位符設置有效時間問題
:即使某線程出現異常,但占位符過瞭有效時間,鎖就會釋放。但是在大量線程同時訪問時,如果線程1被外界因素影響(網絡波動,服務器出問題等等),線程1的業務還沒完成,但鎖的有效時間到瞭的話,下一個線程就會進來,就會出現線程不安全的情況,出現線程互相刪鎖的情況。
@Test public void testLock02() { ValueOperations valueOperations = redisTemplate.opsForValue(); //如果key不存在才可以設置成功,設置一個有效時間防止線程異常出現死鎖 Boolean isLock = valueOperations.setIfAbsent("k1", "v1",5, TimeUnit.SECONDS); //如果占位成功,進行正常操作 if (isLock){ //設置一個name存到redis valueOperations.set("name","xxxx"); //從redis取出name String str = (String) valueOperations.get("name"); System.out.println("name = " + str); //制造異常 Integer.parseInt("xxxx"); //操作結束刪除鎖 redisTemplate.delete("k1"); }else{ System.out.println("有線程在用,請稍後在試"); } }
解決方案:
使用lua腳本,給每個鎖的key對應的value設置一個隨機數
1.3:使用lua腳本解決線程不安全問題:
lua腳本可以寫在Redis服務器上:
優點
: 在服務器上運行速度快
缺點
: 修改代碼時比較麻煩
lua腳本可以通過java發送
優點:
修改代碼方便
缺點:
每次發送請求時都需要占用網絡資源
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同級目錄 redisScript.setLocation(new ClassPathResource("lock.lua")); //設置類型為boolean redisScript.setResultType(Boolean.class); return redisScript; }
1.3.3:編寫測試模塊
@Test public void testLock03(){ ValueOperations valueOperations = redisTemplate.opsForValue(); String value = UUID.randomUUID().toString(); //如果key不存在才可以設置成功,設置一個value為隨機數的值,防止出現線程太多 導致線程不安全 Boolean isLock = valueOperations.setIfAbsent("k1", value, 5, TimeUnit.SECONDS); //如果占位成功,進行正常操作 if (isLock){ //設置一個name存到redis valueOperations.set("name","xxxx"); //從redis取出name String name = (String) valueOperations.get("name"); System.out.println("name = " + name); //為redis發送lua腳本刪除鎖對應的value Boolean aBoolean = (Boolean) redisTemplate.execute(redisScript, Collections.singletonList("k1"), value); System.out.println(aBoolean); }else{ System.out.println("有線程在用,請稍後在試"); } }
測試結果:
順利把name值存到redis中並把鎖刪除並返回true
鎖會被正常刪除隻留下name:
到此這篇關於使用RedisTemplat實現簡單的分佈式鎖的文章就介紹到這瞭,更多相關RedisTemplat分佈式鎖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- redis深入淺出分佈式鎖實現下篇
- SpringBoot集成Redis的思路詳解
- 關於SpringBoot 使用 Redis 分佈式鎖解決並發問題
- Spring整合redis的操作代碼
- Java與SpringBoot對redis的使用方式