大家好,我是沸才,上一篇文章,我给大家分享了redis快速实现分布式锁,但是还存在一些问题。
分布式锁的优化
1.在释放锁时需要去判断这个锁是不是自己的锁
大家想一下线程一去获取锁去执行业务,突然线程一阻塞了,超时释放锁了,线程二可以去获取锁,当这个情况下线程一苏醒了,执行完业务去释放锁,结果,线程二的锁被释放了造成线程安全问题(执行流程如下图)
那么我么们应该根据什么去判断呢?线程id?因为我们本来存入的就是value就是线程id,但是大家想一下,我们在集群模式下有多个jvm,每个jvm都有自己的线程一,线程二,这样会导致线程id一致啊,所以我们存入的value要保证唯一性 ,所以我们需要添加全局唯一id,也就是唯一线程标识,这个就是我们判断是不是自己的锁的依据,全局唯一标识保存在每个独立的服务器下,不同的服务器生成的全局唯一id肯定不一样
- 以前业务流程
- 现在业务流程
需要修改的有两处
- 添加线程唯一标识存入value中
/*
获取锁的方法
**/
public boolean tryLock(long timeoutSec) {
//使用全局唯一生随机数生成器uuid,我这里使用的UUID,也可以用其他方法保证唯一性即可
String LOCK_PREFIX = UUID.randomUUID() + "-";
//获取线程表示,线程标示用于当做value,唯一性
//以前是只存入线程id,现在是uuid(全局唯一id) + 线程id
String threadid = LOCK_PREFIX + Thread.currentThread().getId();
//获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name,threadid,timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
2.释放锁之前需要判断一下是不是自己的锁
/**
* 释放锁
*/
public void unlock() {
//判断reids存入的value和我们本服务器生成的全局唯一id(uuid + 线程id)是否相等
//相等才能去释放锁
if(LOCK_PREFIX + Thread.currentThread().getId() == stringRedisTemplate.opsForValue().get(KEY_PREFIX + name)){
stringRedisTemplate.delete(KEY_PREFIX + name);
}
2.使用lua脚本来保持原子性
好啦这样就解决了,误删锁的问题,你是不是以为就优化完成,完美无缺了?
当然NO!大家想一下,我判断锁和释放锁是并发操作,不是并行操作,有没有可能我判断完锁,准备释放时,线程突然又阻塞了,你说这怎么可能,判断完之后就释放锁了,中间有没有代码
还真有可能线程阻塞那就是极端情况下,java中jvm存在垃圾回收机制的阻塞现象(full gc)
这样就是造成线程停止,然后线程一超时释放锁了,线程二去获取锁执行业务,这时线程一又好了因为线程一已经执行了判断是不是自己的锁,所以不用去比较就直接开始去执行释放锁,好啦,这下好啦又造成线程并发安全问题,
所以我们需要引入lua脚本来保持判断锁和释放锁的原子性
lua脚本也是一种编程语言,但是我们只需要了解它的几个基本用法就可以了,并且idea还帮我们结合了lua脚本,可以在idea中编写lua脚本
lua脚本语法
1.lua脚本中调用redis函数
redis.call('命令名称','key','其他参数'...)
eg:#执行 set name jack
redis.call('set','name','jack')
2.写好lua脚本后指行这个命令
如果脚本中的key,value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在lua脚本中可以从KEYS和ARGV数组获取这些参数:
好啦lua脚本基本语法就是这些啦,如果看上面这些似懂非懂,那就去看官方文档,我们可要养成观看官方文档的好习惯,毕竟以后工作了遇到新的技术不一定都有视频讲解
在分布式锁中使用lua脚本
- 获取锁中的线程标识
- 判断是否与指定的标识一致
- 如果一致释放锁
- 如果不一致则什么都不做
-- 获取锁中的线程标识 get key
local id = redis.call('get',KEYS[1])
-- 比较线程标识与锁中的标识是否一致
if(id == ARGV[1]) then
return redis.call('del',KEYS[1]);
end
-- 如果普配不上返回0
return 0
在文件中写好lua脚本之后我们就开始在业务中调用了,要想在java中使用lua脚本只需使用下面语法
//调用lua脚本
//第一步创建script
DefaultRedisScript<Long> UNLOCK = new DefaultRedisScript<>("unlock.lua",Long.class);
//第二步使用redis命令调用方法
stringRedisTemplate.execute(UNLOCK,
//传入keys,singletonList是把字符串编程list集合
Collections.singletonList(KEY_PREFIX + name),
//传入argv
LOCK_PREFIX + Thread.currentThread().getId());
好啦关于redis实现分布式锁完结啦,如果觉得还不错的麻烦点赞+收藏❤❤❤我是沸才会持续更新有用的编程知识