分布式锁一般用 setnx(set if not exists) 命令,只允许被一个客户端获得锁,先到先得,用完后再用 del 释放锁。
版本1:
# 获取锁127.0.0.1:6379> setnx lock:temp random(integer) 1# ---- 如果得到锁,执行业务代码 ----# ---- 如果没有得到锁,继续尝试获取锁 ----# 释放锁127.0.0.1:6379> del lock:temp(integer) 1
这样就会有一个问题,如果业务代码执行出错没有被捕捉到,或者由于某种特殊情况程序中断,就会导致 del 命令没有执行到,这样就会陷入死锁,锁永远得不到释放。
版本2:
# 获取锁127.0.0.1:6379> setnx lock:temp random(integer) 1# 设置锁的过期时间127.0.0.1:6379> expire lock:temp 5(integer) 1# ---- 如果得到锁,执行业务代码 ----# ---- 如果没有得到锁,继续尝试获取锁 ----# 释放锁127.0.0.1:6379> del lock:temp(integer) 1
版本 2 增加了锁的过期时间,这样出现异常没有正常释放锁的话可以保证一断时间后自动释放锁。但版本 2 还是有一个问题,如果在 setnx 和 expire 之间服务器出现异常,导致 expire 命令没有执行到,也会出现死锁问题。因为 setnx 和 expire 是两条分开执行的指令,不是原子操作。
还有一个问题,如果 setnx 没有拿到锁,expire 是不应该执行的。
版本3:
# 获取锁127.0.0.1:6379> set lock:temp random ex 5 nxOK# ---- 如果得到锁,执行业务代码 ----# ---- 如果没有得到锁,继续尝试获取锁 ----# 释放锁127.0.0.1:6379> del lock:temp(integer) 1
版本 3 使用新的命令 set {key} {value} ex {seconds} nx 解决操作原子性问题。该命令能保证获取锁与设置超时时间操作的原子性。
但是该版本还有问题:
- 如果 A 请求业务代码执行时间超过锁的时间,这时锁会自动释放,这时假如 B 得到锁,B 请求刚得到锁的时候,A 请求的业务代码执行完了,A 请求调用了
del释放锁,这样就会出问题,因为 A 释放的不是自己的锁。 - 如果业务代码出现异常没有被捕获,程序运行就中止了,这样就会导致锁得不到释放,所以在执行业务代码的时候要增加 try catch。
版本4:
```shell获取锁
127.0.0.1:6379> set lock:temp random ex 5 nx OK
—— 如果得到锁,执行业务代码,注意要捕获业务代码的异常 ——
—— 如果没有得到锁,继续尝试获取锁 ——
先获取存入的随机数
127.0.0.1:6379> get lock:temp “random”
情况1:get 命令获取的随机数与存入的随机数一致,说明当前请求没有超时,调用 del 释放锁
127.0.0.1:6379> del lock:temp (integer) 1
情况2:get 命令获取的随机数与存入的随机数不一致,说明当前请求业务代码执行时长超过锁有效时长,
锁已经提前自动释放,所以不用调用 del
<a name="ocvSP"></a>## PHP 分布式锁代码实现:```php/*** 分布式锁模板方法,如果获取不到锁则再次尝试* @param string $key 锁的对象* @param Closure $handle 回调方法(业务方法)* @return mixed 业务方法返回值* @throws Exception*/static public function distributedLockTemplate($key, Closure $handle){// 锁有效时长$time = 5;// 存入随机数,防止业务代码超时释放锁出错$random = GUIDService::getGuid();// 获取锁$count = self::setExNx($key, $random, $time);// 记录返回值$result = null;// 如果获取到锁if ($count > 0) {// 执行业务代码try {$result = $handle();} catch (Exception $exception) {self::releaseLock($key, $random);throw $exception;}self::releaseLock($key, $random);} else {// 如果没有获取到锁,再次尝试获取sleep(0.2);// 递归调用$result = self::distributedLockTemplate($key, $handle);}return $result;}/*** 释放锁* @param $key* @param $random*/static private function releaseLock($key, $random){// 获取随机数,防止业务代码超时释放锁出错$catchRandom = self::get($key);// 对比随机数if ($random == $catchRandom) {// 释放锁RedisService::del($key);}}static public function get($key){return Redis::get($key);}static public function setExNx($key, $value, $second){return Redis::set($key, $value, 'ex', $second, 'nx');}
- 获取不到锁的多种处理方式
- 递归调用重新获取
- 抛异常给用户,提示重试
- 放得任务队列重试
