基于Redis實現(xiàn)分布式鎖的方法(lua腳本版)
1、前言
在Java中,我們通過鎖來避免由于競爭而造成的數(shù)據(jù)不一致問題。通常我們使用synchronized 、Lock來實現(xiàn)。但是Java中的鎖只能保證在同一個JVM進(jìn)程內(nèi)中可用,在跨JVM進(jìn)程,例如分布式系統(tǒng)上則不可靠了。
2、分布式鎖
分布式鎖,是一種思想,它的實現(xiàn)方式有很多,如基于數(shù)據(jù)庫實現(xiàn)、基于緩存(Redis等)實現(xiàn)、基于Zookeeper實現(xiàn)等等。為了確保分布式鎖可用,我們至少要確保鎖的實現(xiàn)同時滿足以下四個條件
- 互斥性:在任意時刻,只有一個客戶端能持有鎖。
- 不會發(fā)生死鎖:即使客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續(xù)其他客戶端能加鎖。
- 具有容錯性:只要大部分的Redis節(jié)點正常運行,客戶端就可以加鎖和解鎖。
- 解鈴還須系鈴人:加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。
3、基于Redis實現(xiàn)分布式鎖
以下代碼實現(xiàn)了基于redis中間件的分布式鎖。加鎖的過程中為了保障setnx(設(shè)置KEY)和expire(設(shè)置超時時間)盡可能在一個事務(wù)中,使用到了lua腳本的方式,將需要完成的指令一并提交到redis中;
3.1、RedisConfig.java
package com.demo.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // key采用String的序列化方式 template.setKeySerializer(new StringRedisSerializer()); // value序列化方式采用jackson template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } }
3.2、RedisLockController.java
package com.demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; @RestController @RequestMapping("/redis") public class RedisLockController { @Autowired private RedisTemplate<String, Object> redisTemplate; @RequestMapping(value = "/lock/{key}/{uid}/{expire}") public Long lock(@PathVariable("key") String key, @PathVariable("uid") String uid, @PathVariable("expire") Integer expire) { Long result = null; try { //調(diào)用lua腳本并執(zhí)行 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class);//返回類型是Long //lua文件存放在resources目錄下的redis文件夾內(nèi) redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_lock.lua"))); result = redisTemplate.execute(redisScript, Arrays.asList(key), uid, expire); System.out.println("lock==" + result); } catch (Exception e) { e.printStackTrace(); } return result; } @RequestMapping(value = "/unlock/{key}/{uid}") public Long unlock(@PathVariable("key") String key, @PathVariable("uid") String uid) { Long result = null; try { //調(diào)用lua腳本并執(zhí)行 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class);//返回類型是Long //lua文件存放在resources目錄下的redis文件夾內(nèi) redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_unlock.lua"))); result = redisTemplate.execute(redisScript, Arrays.asList(key), uid); System.out.println("unlock==" + result); } catch (Exception e) { e.printStackTrace(); } return result; } }
3.3、redis_lock.lua
if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end
3.4、redis_unlock.lua
if redis.call("exists",KEYS[1]) == 0 then return 1 end if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
4、測試效果
key123為key,thread12345為value標(biāo)識鎖的主人,300為該鎖的超時時間
加鎖:鎖主人為thread12345
http://127.0.0.1:8080/redis/lock/key123/thread12345/300
解鎖:解鎖人為thread123456
http://127.0.0.1:8080/redis/unlock/key123/thread123456
解鎖:解鎖人為thread12345
http://127.0.0.1:8080/redis/unlock/key123/thread12345
4.1、加鎖,其他人解鎖
thread12345加的鎖,thread123456是解不了的,只有等thread12345自己解鎖或者鎖的超時時間過期
4.2、加鎖,自己解鎖
thread12345加的鎖,thread12345自己隨時可以解鎖,也可以等鎖的超時時間過期
5、總結(jié)
- 使用Redis鎖,會有業(yè)務(wù)未執(zhí)行完,鎖過期的問題,也就是鎖不具有可重入性的特點。
- 使用Redis鎖,在嘗試獲取鎖的時候,是非阻塞的,不滿足在一定期限內(nèi)不斷嘗試獲取鎖的場景。
- 以上兩點,都可以采用Redisson鎖解決。
到此這篇關(guān)于基于Redis實現(xiàn)分布式鎖的方法(lua腳本版)的文章就介紹到這了,更多相關(guān)Redis實現(xiàn)分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis安裝和配置_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了redis安裝和配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08基于?Spring?Aop?環(huán)繞通知實現(xiàn)?Redis?緩存雙刪功能(示例代碼)
基于 spring aop 常規(guī)應(yīng)用場景多是用于日志記錄以及實現(xiàn) redis 分布式鎖,在 github 中也有項目是把它拿來當(dāng)作緩存的異常捕捉,這篇文章主要介紹了基于?Spring?Aop?環(huán)繞通知實現(xiàn)?Redis?緩存雙刪,需要的朋友可以參考下2022-08-08Redis下載部署并加入idea應(yīng)用的小結(jié)
這篇文章主要介紹了Redis下載部署并加入idea應(yīng)用,需要的朋友可以參考下2022-10-10