做分布式锁的思路:
用一个全局唯一的资源来满足资源竞争的顺序执行和原子性就行。
步骤:
1、A先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。但这种做法可能带来的一个问题:在setnx之后执行expire之前进程意外crash或者要重启维护了,导致永远不释放该锁。
2、为了避免上面提到的问题。setnx时加上一个时间戳,表示失效时间。
SETNX lock.foo <current Unix time + lock timeout + 1>
该锁包含一个Unix时间戳,如果这样一个时间戳等于当前的Unix时间,该锁将不再有效。
3、另一个客户端比如B检测到一个过期的锁并且都尝试去释放它的算法:
-
B发送
SETNX lock.foo
为了获得该锁
-
已经崩溃的客户端 A仍然持有该锁,所以Redis将会返回
0
给B
-
B发送
GET lock.foo
检查该锁是否已经过期。如果没有过期,B客户端将会睡眠一会,并且从一开始进行重试操作
-
另一种情况,如果因为
lock.foo
键的Unix时间小于当前的Unix时间而导致该锁已经过期,会尝试执行以下的操作:GETSET lock.foo <current Unix timestamp + lock timeout + 1>
由于
GETSET
的语意,B会检查已经过期的旧值是否仍然存储在lock.foo
中。如果是的话,B会获得锁 -
如果另一个客户端,假如为 C ,比 B更快的通过
GETSET
操作获取到锁,那么 B执行GETSET
操作会被返回一个不过期的时间戳。C4 将会从第一个步骤重新开始。请注意:即使 B在将来几秒设置该键,这也不是问题。
为了使这种加锁算法更加的健壮,持有锁的客户端应该总是要检查是否超时,保证使用DEL
释放锁之前不会过期,因为客户端故障的情况可能是复杂的,不止是崩溃,还会阻塞一段时间,阻止一些操作的执行,并且在阻塞恢复后尝试执行DEL
(此时,该LOCK已经被其他客户端所持有)