一、为什么需要分布式锁
加入你是一个人买东西的服务,你那你时刻要考虑的是物品会不会买超用户的钱扣除的有没有问题,在单机器部署的情况下你可能只需要加Synchronize就能实现临界区的安全访问,但是现在服务都是集群部署所以这种方式就不适用,我们需要一种方式来锁住所有的实例来实现在不同机器之间的临界区安全访问,分布式锁就是来干这个的。
二、上分布式🔐
1、上🔐
Redis里面有两个命令SETNX,EXPIRE
- SETNX: 当key不存在的时候把这个键值对set进去返回成功,如果命令执行之前key就已经存在了那就返回失败。
- EXPORE:简简单单设置一个过期时间,为啥用这个命令呢其实原因很简单,后面再说。
锁的使用伪代码
if setnx(goods_key, 1):
expire(goods_key, 30)
try:
do something
finally:
del(goods_key)
也就是说当setnx命令执行成功之后才能执行临界区代码,执行完了主动删除掉这个key然后其他的就能拿到这个锁了,看似是写在finally里面的但是finally里面的代码不一定真的能执行完,存在这样几种情况在执行临界区的时候服务被重启了或者服务器突然断电了,或者你finally里面有异常del命令还没有执行到就结束了,就算del被执行了他也不一定能执行成功,如果网络波动一下那这个key就会永久存在好了再也不会有人能拿到锁了GG,所以设置过期时间是非常必要的也是必须的。
上面说到了expire的重要性和必要性,但是这样写没有原子性,是存在setnx执行成功了但是expire没执行成功,那么redis有没有一种方式可以保证这两个命令的原子性呢,那必须的呀要没有也不会有这篇文章,其中lua脚本可以原子性的被redis执行,我们需要在把这两个命令写到lua脚本里面,上脚本!
local key = KEYS[1]
local value = ARGV[1]
local timeout = ARGV[2]
if redis.call('SETNX', key, value) == 1
then
return redis.call('SETEX', key, timeout, value)
else
return 'Fail'
end
这样执行
EVAL “local key = KEYS[1] local value = ARGV[1] local time_out = ARGV[2] if redis.call(‘SETNX’, key, value) == 1 then return redis.call(‘SETEX’, key, time_out, value) else return ‘Fail’ end” 1 hxl np 10
解释一下,EVAL 是redis执行lua脚本的关键字,后面是脚本, 末尾是 “1 hxl np 10” 其中1的意识是后面有1个key剩下的是arg
- 当然用set key value NX EX timeout 也不是不行
2、释放🔐
上面解释了在加锁阶段加入超时时间的必要性,那么在解锁阶段这个超时时间会带我我们什么困扰呢?问题是这样的,因为锁是有超时时间的,例如你🔐的超时时间是500ms但是你执行了600ms,可预知的是如果后面有等待的实例,那么后面等待的那个实例在500ms的时候就可以获取锁成功了(🔐在500ms的时候超时了),然后他开始执行了。当时间点来到600ms的时候第一个执行的实例执行完了他要解🔐了,所以第二个实例上的🔐被第一个实例释放了可以说GG了。
这个问题的解决分两步解决
- 第一不能释放别人的锁还是利用lua脚本,在我们上锁的时候可以生成一个uuid当做value,在我们解锁的时候要判断这个key里面的value是否是我们刚塞进去的如果是的话才能删除
local key = KEYS[1]
local value = ARGV[1]
if redis.call('GET', key) == value
then
redis.call('DEL', key)
end
- 不能在未持有锁的情况下去执行临界区代码,要么你设置的过期时间长一点但是这种方法不太好因为时间短可能执行不完时间长就遇到了最一开始讲的那个问题(为什么锁要加过期时间),要么设置守护线程,在执行没结束且🔐快到期时吼一嗓子,redis给俺加点时间。
3、等待🔐
如果加锁失败你就需要不停的去执行上锁的操作一旦成功则去执行,但是这种while true的操作一直死命循环不但费服务器资源还加重了redis的负担,所以需要一种好的方式来避免这个while true的问题,那么Redis的发布订阅对于你来说就尤其重要。在释放锁的阶段最后发布锁的释放信息,等待的实例收到这个信息之后开始获取锁
4、集群部署还存在的问题
在主从的模式下,如果在主从切换的时候会发生这样的问题,锁设置成功了还没来的及通知从节点主节点挂掉了,这个时候从节点成主节点了,由于没有收到主节点的通知,所以这个时候从节点是可以加锁成功的。