分布式锁
锁是什么
锁是解决多线程问题,多线程去处理同一处代码,因为线程是无序的会造成数据错乱,java里处理这种问题有synchronized和ReentrantLock还有threadLocal等等去解决线程安全问题。
但是当项目的用户越来越多,系统架构采用分布式,单单synchronized和RenntrantLock并不能解决分布式中的资源抢夺。所以有了新的解决方案。
基于数据库的分布式锁
redis分布式锁
zookeeper分布式锁
这里先介绍redis分布式锁。
redis分布式锁
redis中的get和set命令并不是原子性的,众所周知锁必须满足原子性(关于原子性是什么可以百度下)。
那么redis怎么去实现原子性操作呢。
执行setnx ->执行业务代码->执行del
在setnx和del命令中执行的你业务代码。
SETNX key value
返回值:
key不存在 = 1
key存在 = 0
这里存在一个问题啊。
如果在执行setnx之后断点了。del命令并没有执行,会造成死锁,锁得不到释放,会一直占用资源。
所以在setnx之后设置一个expire存活时间
->setnx key value
ok
->expire key 5
这里写上你的业务代码........
->del key
(integer) 1
但是还有存在一个问题.
如果在setnx和expire之间机器挂掉,还是会造成死锁.因为setnx和expire操作并不是同时执行的,不是原子操作.
所以在redis2.8version中加入了set执行的扩展函数,保证setnx和expire可以同时执行.彻底解决了分布式锁的乱象.
->set key value ex 5 nx
OK
执行你的业务代码
->del key
(integer) 1
综上所述,setnx和expire两个同时执行就是redis分布式锁的奥秘所在
为了保证超时问题,第一个线程持有的锁过期了,导致代码没有执行完,而同时第二个线程提前重新持有了这把锁,在set指令的时候为value设定一个随机值,释放锁的时候匹配随机值,然后在删除key,这是为了确保当前线程占有的锁不会被其他线程释放,除非这个锁是过期释放的。
random.nextint()
但是匹配随机值和删除key并不是一个原子操作,redis提供了lua脚本来实现原子操作
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
以上。
这里有个tip,经验所得,如果不知道过期时间等于多少的话,
可以这样设置
过期时间=业务处理时间*2