redis实现分布式锁
1.redis分布式锁
redis可以使用setnx命令实现分布式锁。
setnx的意思是如果不存在则设置值。通过设置一个key value作为锁。当这个key存在的时候,就说明加锁了,当这个key不存在的时候,就说明没加锁,那就设置key value进行上锁。
即
> setnx lock true
OK
... do something critical ...
> del lock
(integer) 1
2. setnx存在的问题(expire命令)
假如在上锁之后,指定代码逻辑过程中这台机器宕机挂掉了,导致锁一值存在没有释放,由于是分布式环境,那么其他服务器一致抢占不到锁,导致死锁,
2.1 解决办法
可以加上一个锁的过期时间,在指定的时间之内如果锁没释放的话,就会自动释放锁。
可以使用redis提供的expire命令。
即
> setnx lock true
OK
> expire lock 5
... do something critical ...
> del lock
(integer) 1
3.expire命令存在的问题(使用setnx key value ex 时间 nx)
由于setnx和expire是两条命令,并不是原子操作,还是存在在执行完setnx命令之后,服务器挂掉导致没执行expire命令的隐患。这时可以使用redis提供的一个原子操作,将setnx和expire变成一个原子操作命令
> set lock true ex 5 nx
OK
... do something critical ...
> del lock
4.redis实现的分布式并不能解决超时问题
如果加锁和解锁之间的代码逻辑执行过久,导致超出了锁的过期时间,那么会导致锁被释放,然后其他机器拿key进行加锁,然后再其他机器执行过程中,第一台超时导致锁被释放的机器代码执行完了,然后执行del key操作,把其他机器拿到的锁给释放了,存在线程不安全问题。
一个解决思路是,设置的key为一串随机数,然后在释放锁的时候匹配随机数是否一致,一致再删除key。但是匹配和删除并不是一个原子操作。这时候就需要使用Lua脚本来处理了,因为Lua脚本可以保证连续多个指令的原子执行
tag = random.nextint() # 随机数
if redis.set(key, tag, nx=True, ex=5):
do_something()
redis.delifequals(key, tag) # 假象的 delifequals 指令
# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])