SpringBoot+Redis实现分布式锁
一:Lua脚本
加锁:
--[[ 思路: 1.用2个局部变量接受参数 2.由于redis内置lua解析器,执行加锁命令 3.如果加锁成功,则设置超时时间 4.返回加锁命令的执行结果 ]] local key = KEYS[1] local value = KEYS[2] local rs1 = redis.call('SETNX',key,value) if rs1 == true then redis.call('SETEX', key,3600, value) end return rs1
解锁:
--[[ 思路: 1.接受redis传来的参数 2.判断是否是自己的锁,是则删掉 3.返回结果值 ]] local key = KEYS[1] local value = KEYS[2] if redis.call('get',key) == value then return redis.call('del',key) else return false end
SpringBoot测试类:
@SpringBootTest public class TestApplicationTests { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private RedisTemplate redisTemplate; private static String LOCK_PREFIX = "lock_"; private static String lockPath = "lock.lua"; private static String unlockPath = "unlock.lua"; public void execJob() throws IOException { //1.先去获取锁 String key = LOCK_PREFIX + "001"; String value = "job002"; Boolean rs = lock(key, value,lockPath); System.out.println(rs); try { if (!rs) { String string = redisTemplate.opsForValue().get(key).toString(); logger.info(string); } else { logger.info("加锁成功,休息5秒"); Thread.sleep(5000); } } catch (InterruptedException e) { e.printStackTrace(); }finally { //释放锁 logger.info("释放锁"); Boolean rs1 = lock(key, value,unlockPath); System.out.println(rs1); } } public Boolean lock(String key, String value,String luaPath) throws IOException { DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<>(); ClassPathResource resource = new ClassPathResource(luaPath); ResourceScriptSource source = new ResourceScriptSource(resource); lockScript.setScriptSource(source); lockScript.setResultType(Boolean.class); Boolean result = (Boolean) redisTemplate.execute(lockScript, Arrays.asList(key, value)); return result; } @Test public void contextLoads() throws IOException { execJob(); } }
java日志
true 2020-04-16 14:54:45.823 INFO 14988 --- [ main] com.example.test.TestApplicationTests : 加锁成功,休息5秒 2020-04-16 14:54:50.823 INFO 14988 --- [ main] com.example.test.TestApplicationTests : 释放锁 true
在睡眠的5秒钟,一直查keys,发现先有锁,后面会释放锁
27.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\block_001" 2) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\block_001" 2) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\block_001" 2) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\block_001" 2) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\block_001" 2) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\block_001" 2) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x0212" 127.0.0.1:6379>
二 Lua的字符串
/** * @author WGR * @create 2020/4/16 -- 16:50 */ @SpringBootTest public class RedisTest { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private RedisTemplate redisTemplate; private static String LOCK_PREFIX = "lock_"; private static String lockPath = "lock.lua"; public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == KEYS[2] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return false "); sb.append("end "); UNLOCK_LUA = sb.toString(); } public void execJob() throws IOException { //1.先去获取锁 String key = LOCK_PREFIX + "001"; String value = "job002"; Boolean rs = lock(key, value,lockPath); try { if (!rs) { String string = redisTemplate.opsForValue().get(key).toString(); logger.info(string); } else { logger.info("加锁成功,休息5秒"); Thread.sleep(5000); } } catch (InterruptedException e) { e.printStackTrace(); }finally { //释放锁 logger.info("释放锁"); DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(UNLOCK_LUA,Boolean.class); Boolean result = (Boolean)redisTemplate.execute(redisScript, Arrays.asList(key, value)); System.out.println(result); } } public Boolean lock(String key, String value,String luaPath) throws IOException { DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<>(); ClassPathResource resource = new ClassPathResource(luaPath); ResourceScriptSource source = new ResourceScriptSource(resource); lockScript.setScriptSource(source); lockScript.setResultType(Boolean.class); Boolean result = (Boolean) redisTemplate.execute(lockScript, Arrays.asList(key, value)); return result; } @Test public void contextLoads() throws IOException { execJob(); } }
2020-04-16 17:03:51.214 INFO 17376 --- [ main] com.example.test.RedisTest : 加锁成功,休息5秒 2020-04-16 17:03:56.214 INFO 17376 --- [ main] com.example.test.RedisTest : 释放锁 true
注:execute可以换成多个参数的
//注意脚本中KYS[l]和KYS[2] 的写法,它们代表客户端传递的第一个键和第二个键, //而ARGV[l]和ARGV[2]则表示客户端传递的第一个和第二个参数