setnx是不存在才会设置成功。具备判断锁是否存在和上锁的功能。
通过 expire 设置超时解锁,防止意外退出导致的死锁。让后面的任务无法得到锁,阻塞后续任务执行。
…ing…
需要使用lua脚本,因为要保证原子性。
这个脚本判断锁的值是否和当前线程的unqiueId相等,相等就调用del删除锁
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
不使用lua,可能造成删除掉其它线程的锁。
假设A线程任务执行完,判断锁确实是A的锁。准备删除前锁刚好过期,这时B线程上了锁,这时候A线程的删锁操作直接就把B刚上的锁kill了。这就是不使用lua导致判断和删除之间插入了其它操作。
$redis = new Redis();
$connect = $redis->connect("localhost", 6379, 2.5);
//这里方便就这么写了,生产不能这么写
function createUniqueId()
{
return random_int(100000000, 999999999);
}
class RedisLock
{
/** @var Redis */
private $redis;
private $key = "fbs_key";
private $timeOut = 10;
public function __construct($redis)
{
$this->redis = $redis;
}
public function runJob(\Closure $job)
{
$uid = createUniqueId();
if ($this->tryGetLock($uid)) {
$job();
} else {
//为了方便让其递归,生产也不能这么写
sleep(1);
return $this->runJob($job);
}
$this->deleteLock($uid);
}
protected function deleteLock($uid)
{
$lua = <<<SCRIPT
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
SCRIPT;
$this->redis->eval($lua, [$this->key, $uid], 1);
}
protected function tryGetLock($uid)
{
$bool = $this->redis->setnx($this->key, $uid);
if ($bool) $this->redis->expire($this->key, $this->timeOut);
return $bool;
}
}
$lock = new RedisLock($redis);
$lock->runJob(function () {
sleep(5);
echo "执行完成哈";
});