RedisLock.php
2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<?php
namespace common\components\redislock;
use Yii;
use yii\base\Component;
use yii\redis\Connection;
use Exception;
use function class_exists;
use function is_object;
use function uniqid;
use function microtime;
use function mt_rand;
use function floor;
use function usleep;
/**
* 实现Redis事务锁
* Created by PhpStorm.
* User: weigong
* Date: 18/5/9
* Time: 下午3:01
*/
class RedisLock extends Component
{
public $redis;
public $retryDelay;
public $retryCount;
private $clockDriftFactor = 0.01;
public function init()
{
if (!class_exists('\yii\redis\Connection')) {
throw new Exception('the extension yii\redis\Connection does not exist ,you need it to operate redis ,you can run "composer require yiisoft/yii2-redis" to gei it!');
}
parent::init();
if (!is_object($this->redis)) {
$this->redis = Yii::createObject($this->redis);
if(!$this->redis || !$this->redis instanceof Connection){
throw new Exception('Redis class injected must be instanceof of "yii\redis\Connection"');
}
}
}
public function lock($resource, $ttl)
{
$token = uniqid();
$retry = $this->retryCount;
do {
$hasGetLock = false;
$startTime = microtime(true) * 1000;
if ($this->lockInstance($resource, $token, $ttl)) {
$hasGetLock = true;
}
# 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 ($hasGetLock && $validityTime > 0) {
return [
'validity' => $validityTime,
'resource' => $resource,
'token' => $token,
];
} else {
$this->unlockInstance($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)
{
$resource = $lock['resource'];
$token = $lock['token'];
$this->unlockInstance($resource, $token);
}
private function lockInstance($resource, $token, $ttl)
{
return $this->redis->set($resource, $token, 'PX', $ttl, 'NX');
}
private function unlockInstance($resource, $token)
{
$script = '
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
';
return $this->redis->eval($script, 1, $resource, $token);
}
}