实现方案
一、mysql实现
二、redis
三、zookeeper
以下主要说一下redis的实现方式。
需要实现的功能
1、只有一个线程能获取到锁,获取不到锁的线程可以自旋一定的时间,如果仍获取不到,则返回获取不到锁。
2、为了防止业务挂了,导致锁没有释放,需要给锁加上过期时间。
3、只能解锁自己加的锁,可以通过加锁时传入请求标识实现
4、实现锁的可重入
问题
1、如何保证加锁和设置超时时间的原子性?
(1)使用lua脚本
(2)高级版本的redis,支持setnx时,同时设置超时时间。
2、解锁时,如何防止被其他客户端解锁?
加锁时,传入请求标识,做为value存储到redis,解锁时,传入请求标识,通过和redis的请求标识进行对比,如果一致,则认为是同一个请求,允许解锁,否则不允许解锁。
但注意:判断请求标识和redis中的value是否一致,一致需要做删除。这块需要是原子性(一般使用调用lua脚本实现),否则有可能把其他请求的锁删除了。考虑如下场景:
(1)请求1获取了锁,准备删除锁
(2)锁过期了
(3)请求2获取了锁
(4)请求1删除了锁,这把锁已经变了,不是请求1的,而是请求2的,也就是请求1删除了请求2的锁。
3、如何实现超时时间的续租?
在加锁成功后,启动一个守护线程,当时间到了的时候,去延迟锁的时间,当业务执行完成后,停止线程。
问题(1):如果执行一半时,服务挂了,导致未解锁。
此时,因服务挂了,线程也就自动停止了。
问题(2):如果看门狗线程挂了,如何保证可靠性?
4、如何实现可重入锁
可以使用计数器,进来一次,加1,出去一次,减1
5、高可用锁如何实现?
比如请求1获取到锁,写到了redis的master节点,此时还没来得及同步到slave,master就挂了,而slave此时如果成为了master,此时,请求2进来,从新的master上获得了锁。此场景下,两个请求都得到了锁了,业务执行必然出现问题。
参考:RedLock->redission看门狗的设计