| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- <?php
- declare (strict_types = 1);
- class RedLock
- {
- private $retryDelay;
- private $retryCount;
- private $clockDriftFactor = 0.01;
- private $quorum;
- private $servers = array();
- private $instances = array();
- function __construct( $retryDelay = 200, $retryCount = 3)
- {
- $this->servers = [
- [
- 'host' => env('redis.host','127.0.0.1'),
- 'port' => env('redis.port', 6379),
- 'password' => env('redis.password', '123456'),
- 'select' => env('redis.select', 10),
- 'timeout' => env('redis.timeout', 1000),
- ]
- ];
- $this->retryDelay = $retryDelay;
- $this->retryCount = $retryCount;
- $this->quorum = min(count($this->servers), (count($this->servers) / 2 + 1));
- }
- public function lock($resource, $ttl = 600)
- {
- $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(floor($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) {
- $redis = new \think\cache\driver\Redis($server);
- $this->instances[] = $redis;
- }
- }
- }
- 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);
- }
- }
|