| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- <?php
- declare (strict_types=1);
- namespace redis;
- use think\Env;
- class RedLock
- {
- private $retryDelay;
- private $retryCount;
- private $clockDriftFactor = 0.01;
- private $quorum;
- private $servers;
- private $instances = array();
- function __construct($retryDelay = 200, $retryCount = 3)
- {
- $this->servers = [
- [
- 'host' => Env::get('redis.host') ?? '127.0.0.1',
- 'port' => Env::get('redis.port') ?? '6379',
- 'password' => Env::get('redis.password') ?? null,
- 'select' => Env::get('redis.select') ?? 1,
- 'timeout' => Env::get('redis.timeout') ?? 500
- ]
- ];
- $this->retryDelay = $retryDelay;
- $this->retryCount = $retryCount;
- $this->quorum = min(count($this->servers), (count($this->servers) / 2 + 1));
- }
- static function of()
- {
- return new RedLock();
- }
- public function lock($resource, $ttl = 800)
- {
- $this->initInstances();
- $token = uniqid();
- $retry = $this->retryCount;
- do {
- $n = 0;
- $startTime = microtime(true) * 1000;
- foreach ($this->instances as $instance) {
- if ($this->lockInstance($instance, $resource, $token, $ttl)) {
- $n++;
- }
- }
- # Add 2 milliseconds to the drift to account for Redis expires
- # precision, which is 1 millisecond, plus 1 millisecond min drift
- # for small TTLs.
- $drift = ($ttl * $this->clockDriftFactor) + 2;
- $validityTime = $ttl - (microtime(true) * 1000 - $startTime) - $drift;
- if ($n >= $this->quorum && $validityTime > 0) {
- return [
- 'validity' => $validityTime,
- 'resource' => $resource,
- 'token' => $token,
- ];
- } else {
- foreach ($this->instances as $instance) {
- $this->unlockInstance($instance, $resource, $token);
- }
- }
- // Wait a random delay before to retry
- $delay = mt_rand((int)($this->retryDelay / 2), $this->retryDelay);
- usleep($delay * 1000);
- $retry--;
- } while ($retry > 0);
- return false;
- }
- public function unlock(array $lock)
- {
- $this->initInstances();
- $resource = $lock['resource'];
- $token = $lock['token'];
- foreach ($this->instances as $instance) {
- $this->unlockInstance($instance, $resource, $token);
- }
- }
- private function initInstances()
- {
- if (empty($this->instances)) {
- foreach ($this->servers as $server) {
- $this->instances[] = RedisClient::of($server);
- }
- }
- }
- private function lockInstance($instance, $resource, $token, $ttl)
- {
- return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
- }
- private function unlockInstance($instance, $resource, $token)
- {
- $script = '
- if redis.call("GET", KEYS[1]) == ARGV[1] then
- return redis.call("DEL", KEYS[1])
- else
- return 0
- end
- ';
- return $instance->eval($script, [$resource, $token], 1);
- }
- }
|