初初级分布式锁
1.setnx key value // 设置成功返回1,设置失败返回0
2. do something ... // 进行逻辑操作
3.del key // 删除之前设置的key,即删除分布式锁
初初级分布式锁使用setnx + del
完成,先使用setnx
设置key,如果设置成功,就开始进行逻辑操作,逻辑操作完成后,使用del
删除分布式锁。但如果在使用setnx
加锁后,由于某些原因程序终止运行了,那么del
解锁命令就永远得不到执行,那么之前加的分布式锁就永远在redis中了,其他业务也就永远无法获得锁。
为了解决del
命令可能未执行导致锁永远无法释放的问题,于是使用到了expire
命令,给key加过期时间,防止del
命令未运行出现的死锁情况。
初级分布式锁
1.setnx key value // 设置成功返回1,设置失败返回0
2.expire key seconds // 为key设置过期时间
3. do something ... // 进行逻辑操作
4.del key // 删除之前设置的key,即删除分布式锁
初级分布式锁使用setnx + expire + del
完成,首先setnx
设置key,如果设置成功则调用expire
为该key设置过期时间,然后开始做一些逻辑操作,最后相关逻辑操作完成后使用del
删除key来释放分布式锁。
初级分布式锁很清晰的逻辑,但是首先要明白,在setnx
、expire
两个命令之间也会因为某些原因导致业务中断,程序就没法正常进行下去,也就会导致分布式锁失效。比方说:
setnx
成功后,开始设置过期时间应该,但是这时候服务器宕机了,导致expire
命令未执行,于是,之前设置的key就会永远存在redis中,当服务器恢复正常后,正常业务就再也无法获得锁,因为他发现,这个key一直存在,再使用setnx
就一直失败。
expire
命令未正常运行,这就和第一种“初初级分布式锁”仅用setnx + del
情况是一毛一样了,出现了死锁。
中级分布式锁
1.set key value ex seconds nx // 设置成功返回1,设置失败返回nil
2. do something ... // 进行逻辑操作
3.del key // 删除之前设置的key,即删除分布式锁
中级分布式锁的基础是redis版本已经升级到2.6.12版本以上,set
命令在该版本之前还仅是set key value
的操作模式,升级后就加了ex和nx等参数,使用ex和nx参数后等效于
setnx key value; expire key seconds
两条命令,且使用set
实现ex和nx的功能都是原子性的,不会出现ex和nx断档。
该分布式锁虽然解决了setnx
和expire
两个命令的断档问题,但是如果do something
的逻辑操作执行时间过长会出现什么新的问题?
- 问题一:业务A
do something
时间超出设置的过期时间,如果这时候业务逻辑还没做完,业务B就来获取锁了,然后也会走do something
,这时候就会出现业务数据紊乱,也就打破了使用分布式锁的初衷 - 问题二:业务A
do something
时间超出设置的过期时间,业务A的分布式锁被释放,业务B获取了该锁,正在执行,这时候业务Ado something
完成了,开始释放锁,结果把已经获取分布式锁的业务B的锁给释放了
如何解决上述问题呢?
方法一:
针对问题二,可以在加锁操作时,给key的value设置一个随机数并记录下来,当do something
走完后,要释放锁时,先判断分布式锁的value和当前要删除的key的value,也就是之前设置的随机数是否一致,如果一致才可以删除,如果删除不了,就说明这个key已经过期了,被自动释放了。这个方法仍旧有一个弊端,就是在判断value是否一致时的操作也不是原子型的,假如业务A的锁还没过期,然后最后要开始释放锁,他先判断value是否一致,发现value是一致的,但是因为某些原因导致要过N长时间才能执行del
操作,然后业务A的锁到期了自动释放了,而这时候业务B获取了锁,结果业务A的del
操作终于苏醒了,于是他开始释放锁,结果把业务B的锁给释放了,针对这个问题,说是可以用lua脚本来解决,因为 Lua 脚本可以保证连续多个指令的原子性执行(但是我目前还不会lua脚本)
方法二:
针对问题一的数据错乱,那目前没招... 要么就是在业务层做控制,比方说在do something
工作期间判断锁是否有效,无效的话就回滚当前业务,抛出异常,还比方说自动续期