2021-05-20 16:53:34 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Cache\Repository;
|
|
|
|
|
|
|
|
use App\Cache\Contracts\LockRedisInterface;
|
2021-07-08 22:28:51 +08:00
|
|
|
use App\Traits\StaticInstance;
|
2021-05-25 18:20:55 +08:00
|
|
|
use Swoole\Coroutine;
|
|
|
|
use Exception;
|
2021-05-20 16:53:34 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Redis Lock
|
|
|
|
*
|
|
|
|
* @package App\Cache\Repository
|
|
|
|
*/
|
2021-07-08 22:28:51 +08:00
|
|
|
final class LockRedis extends AbstractRedis implements LockRedisInterface
|
2021-05-20 16:53:34 +08:00
|
|
|
{
|
2021-05-21 22:56:42 +08:00
|
|
|
protected $prefix = 'rds-lock';
|
2021-05-20 16:53:34 +08:00
|
|
|
|
2021-05-25 18:20:55 +08:00
|
|
|
protected $value = 1;
|
2021-05-20 16:53:34 +08:00
|
|
|
|
2021-07-08 22:28:51 +08:00
|
|
|
use StaticInstance;
|
|
|
|
|
2021-05-20 16:53:34 +08:00
|
|
|
/**
|
|
|
|
* 获取 Redis 锁
|
|
|
|
*
|
2021-05-25 18:20:55 +08:00
|
|
|
* @param string $key 锁标识
|
|
|
|
* @param int $expired 过期时间/秒
|
|
|
|
* @param int $timeout 获取超时/秒,默认每隔 0.1 秒获取一次锁
|
2021-05-20 16:53:34 +08:00
|
|
|
* @return bool
|
|
|
|
*/
|
2021-05-25 18:20:55 +08:00
|
|
|
public function lock(string $key, $expired = 1, int $timeout = 0)
|
2021-05-20 16:53:34 +08:00
|
|
|
{
|
|
|
|
$lockName = $this->getCacheKey($key);
|
|
|
|
|
2021-05-25 18:20:55 +08:00
|
|
|
// 重复获取次数
|
|
|
|
$retry = $timeout > 0 ? intdiv($timeout * 100, 10) : 1;
|
2021-05-20 16:53:34 +08:00
|
|
|
do {
|
2021-05-25 18:20:55 +08:00
|
|
|
$lock = $this->redis()->set($lockName, $this->value, ['nx', 'ex' => $expired]);
|
2021-05-20 16:53:34 +08:00
|
|
|
if ($lock || $timeout === 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 默认 0.1 秒一次取锁
|
2021-05-25 18:20:55 +08:00
|
|
|
Coroutine::getCid() ? Coroutine::sleep(0.1) : usleep(100000);
|
|
|
|
|
|
|
|
$retry--;
|
|
|
|
} while ($retry);
|
2021-05-20 16:53:34 +08:00
|
|
|
|
|
|
|
return $lock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 释放 Redis 锁
|
|
|
|
*
|
|
|
|
* @param string $key
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function delete(string $key)
|
|
|
|
{
|
|
|
|
$script = <<<LAU
|
|
|
|
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
|
|
return redis.call("DEL", KEYS[1])
|
|
|
|
else
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
LAU;
|
|
|
|
|
2021-05-25 18:20:55 +08:00
|
|
|
return $this->redis()->eval($script, [$this->getCacheKey($key), $this->value], 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 获取锁并执行
|
|
|
|
*
|
|
|
|
* @param \Closure $closure 闭包函数
|
|
|
|
* @param string $lock_name 锁名
|
|
|
|
* @param int $expired 过期时间/秒
|
|
|
|
* @param int $timeout 获取超时/秒
|
|
|
|
* @return bool
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function try(\Closure $closure, string $lock_name, int $expired = 1, int $timeout = 0)
|
|
|
|
{
|
|
|
|
if (!$this->lock($lock_name, $expired, $timeout)) return false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
call_user_func($closure);
|
|
|
|
} catch (Exception $e) {
|
|
|
|
throw $e;
|
|
|
|
} finally {
|
|
|
|
$this->delete($lock_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2021-05-20 16:53:34 +08:00
|
|
|
}
|
|
|
|
}
|