【Demo不推荐】使用Redis的setnx指令实现 分布式锁 解决一人一单问题
1 原理:setnx指令
setnx指令的特点:setnx只能设置key不存在的值,值不存在设置成功,返回 1 ;值存在设置失败,返回 0
文档地址 https://redis.io/docs/latest/commands/setnx/
如果键不存在,则将键设置为保存字符串值。在这种情况下,它相当于 SET。如果键已保存值,则不执行任何操作。SETNX 是“SET if Not eXists”的缩写。
Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if Not eXists".
获取锁:
方式一:
# 添加锁
setnx [key] [value]
# 为锁设置过期时间,超时释放,避免死锁
expire [key] [time]
方式二:这种方式更加推荐,因为将上面两个指令变成一个指令,从而保障指令的原子性
# 添加锁
set [key] [value] ex [time] nx
释放锁:
# 释放锁(除了使用del手动释放,还可超时释放)
del [key]
2 实现过程
2.1 简陋版1(有问题,释放了别人的锁)
- 注意点:try...finally...确保发生异常时锁能够释放,注意这给地方不要使用catch,A事务方法内部调用B事务方法,A事务方法不能够直接catch,否则会导致事务失效。
- 学习自 https://blog.csdn.net/qq_66345100/article/details/131986713?spm=1001.2014.3001.5506
有问题:
- 当线程1获取锁后,由于业务阻塞,线程1的锁超时释放了,
- 这时候线程2趁虚而入拿到了锁,然后此时线程1业务完成了,然后把线程2刚刚获取的锁给释放了,
- 这时候线程3又趁虚而入拿到了锁,这就导致又出现了超卖问题!(但是这个在小项目(并发数不高)中出现的概率比较低,在大型项目(并发数高)情况下是有一定概率的)
2.2 简陋版2(有问题, 判断锁和释放锁可能被打断,为保证原子性)
为分布式锁添加一个线程标识,
- 在释放锁时判断当前锁是否是自己的锁,是自己的就直接释放,
- 不是自己的就不释放锁,从而解决多个线程同时获得锁的情况导致出现超卖

实现思路:
- 加锁时
- Redis中key为锁名
- uuid+Thread的id作为redis中的value
- 释放锁时
- uuid+Thread的id和Redis中的值比较,一致再释放锁。
仍然有问题:
- 线程1获取锁,执行完业务然后并且判断完当前锁是自己的锁时,但就在此时发生了阻塞,结果锁被超时释放了,
- 线程2立马就趁虚而入了,获得锁执行业务,
- 但就在此时线程1阻塞完成,由于已经判断过锁,已经确定锁是自己的锁了,于是直接就删除了锁,结果删的是线程2的锁,
- 这就又导致线程3趁虚而入了,从而继续发生超卖问题
产生原因:
判断锁和释放锁在同一个方法中,并且两者之间没有别的代码,为什么会发生阻塞呢?
- JVM的垃圾回收机制会导致短暂的阻塞(csdn作者个人感觉这种情况发生的概率真的不高)
2.3 简陋版3
-
该如何保障 判断锁 和 释放锁 这连段代码的原子性呢?
- 使用Lua脚本
-
那么Lua脚本是如何确保原子性的呢?
- Redis使用(支持)相同的Lua解释器,来运行所有的命令。
- Redis还保证脚本以原子方式执行:在执行脚本时,不会执行其他脚本或Redis命令。这个语义类似于MULTI(开启事务)/EXEC(触发事务,一并执行事务中的所有命令)。从所有其他客户端的角度来看,脚本的效果要么仍然不可见,要么已经完成。
注意:虽然Redis在单个Lua脚本的执行期间会暂停其他脚本和Redis命令,以确保脚本的执行是原子的,但如果Lua脚本本身出错,那么无法完全保证原子性。也就是说Lua脚本中的Redis指令出错,会发生回滚以确保原子性,但Lua脚本本身出错就无法保障原子性
- 实现
释放锁的业务流程是这样的:
- 获取锁中的线程标示
- 判断是否与指定的标示(当前线程标示)一致
- 如果一致则释放锁(删除)
- 如果不一致则什么都不做
此时满足了
- 多线程可见,将锁放到Redis中,所有的JVM都可以同时看到
- 互斥,set ex nx指令互斥
- 高可用,层层优化,即使是特别极端的情况下照样可以防止超卖
- 高性能,Redis的IO速度很快,Lua脚本的性能也很快
- 安全性,这个不用多说了,通过给锁夹线程标识+Lua封装Redis指令充分保障了线程安全,不那么容易出现并发安全问题,同时采用超时释放避免死锁

浙公网安备 33010602011771号