Passed
Push — master ( 25592c...a81e56 )
by wen
01:25
created

Lock::acquireLock()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 3
dl 0
loc 15
rs 9.9
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author: jiangyi
4
 * @date: 下午2:55 2019/2/28
5
 */
6
7
namespace EasyRedis;
8
9
class Lock
10
{
11
    protected $redis;
12
13
    public function __construct(Connection $connection)
14
    {
15
        $this->redis = $connection;
16
    }
17
18
    /**
19
     * 获取锁
20
     * @param string $lockName
21
     * @param int|float $acquireTimeout
22
     * @param int|float $lockTimeout
23
     * @return bool|string
24
     */
25
    public function acquireLock(string $lockName, $acquireTimeout = 10, $lockTimeout = 10)
26
    {
27
        $lockName = 'lock:' . $lockName;
28
        $identifier = $this->getIdentifier();
29
        $end = time() + $acquireTimeout;
30
        while (time() < $end) {
31
            if ($this->redis->setnx($lockName, $identifier)) {
32
                $this->redis->expire($lockName, $lockTimeout);
33
                return $identifier;
34
            } elseif (!$this->redis->ttl($lockName)) {
35
                $this->redis->expire($lockName, $lockTimeout);
36
            }
37
            usleep(1000);
38
        }
39
        return false;
40
    }
41
42
    /**
43
     * 释放锁
44
     * @param string $lockName
45
     * @param string $identifier
46
     * @return bool
47
     */
48
    public function releaseLock(string $lockName, string $identifier)
49
    {
50
        $lockName = 'lock:' . $lockName;
51
        while (true) {
52
            $this->redis->watch($lockName);
53
            if ($this->redis->get($lockName) == $identifier) {
54
                $this->redis->multi();
55
                $this->redis->del($lockName);
56
                if (!$this->redis->exec()) {
57
                    continue;
58
                }
59
                return true;
60
            }
61
            $this->redis->unwatch();
62
            break;
63
        }
64
        return false;
65
    }
66
67
68
    /**
69
     * 获取公平信号量锁
70
     * @param $semname
71
     * @param $limit
72
     * @param int $timeout
73
     * @return bool|string
74
     */
75
    public function acquireFairSemaphore($semname, $limit, $timeout = 10)
76
    {
77
        $identifier = $this->getIdentifier();
78
        $microTime = $this->microTimeFloat();
79
        $ownerZset = $semname . ':owner';
80
        $counterStr = $semname . ':counter';
81
82
        // 清理过期的信号量持有者
83
        $this->redis->zremrangebyscore($semname, '-inf', $microTime + $timeout);
84
        // 计数器自增
85
        $counter = $this->redis->incr($counterStr);
86
        // 加入超时有序集合
87
        $this->redis->zadd($semname, $microTime, $identifier);
88
        // 加入信号量拥有者
89
        $this->redis->zadd($ownerZset, $counter, $identifier);
90
        // 交集
91
        $this->redis->zinterstore($ownerZset, 2, $ownerZset, $semname, 'WEIGHTS', 1, 0);
92
93
        $rank = $this->redis->zrank($ownerZset, $identifier);
94
        if ($rank < $limit) {
95
            return $identifier;
96
        }
97
        // 获取失败,删除添加的信号量
98
        $this->redis->zrem($semname, $identifier);
99
        $this->redis->zrem($ownerZset, $identifier);
100
        return false;
101
    }
102
103
    /**
104
     * 释放公平信号量
105
     * @param $semname
106
     * @param $identifier
107
     * @return mixed
108
     */
109
    public function releaseFairSemaphore($semname, $identifier)
110
    {
111
        $this->redis->zrem($semname, $identifier);
112
        $ownerZset = $semname . ':owner';
113
        return $this->redis->zrem($ownerZset, $identifier);
114
    }
115
116
    /**
117
     * 刷新信号量
118
     * @param $semname
119
     * @param $identifier
120
     * @return bool
121
     */
122
    public function refreshFairSemaphore($semname, $identifier)
123
    {
124
        $microTime = $this->microTimeFloat();
125
        // 更新客户端持有的信号量
126
        if ($this->redis->zadd($semname, $microTime, $identifier)) {
127
            // 告知调用者,客户端已经失去信号量
128
            $this->releaseFairSemaphore($semname, $identifier);
129
            return false;
130
        }
131
        return true;
132
    }
133
134
    /**
135
     * 公平信号量加锁,消除竞争条件
136
     * @param $semname
137
     * @param $limit
138
     * @param int $timeout
139
     * @return bool|string
140
     */
141
    public function acquireSemaphoreWithLock($semname, $limit, $timeout = 10)
142
    {
143
        if ($identifier = $this->acquireLock($semname, 0.01)) {
144
            try {
145
                return $this->acquireFairSemaphore($semname, $limit, $timeout);
146
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
147
148
            } finally {
149
                $this->releaseLock($semname, $identifier);
150
            }
151
        }
152
        return false;
153
    }
154
155
    protected function microTimeFloat()
156
    {
157
        list($usec, $sec) = explode(' ', microtime());
158
        return ((float)$usec + (float)$sec);
159
    }
160
161
    protected function getIdentifier()
162
    {
163
        return self::uuidV4();
164
    }
165
166
    public static function uuidV4()
167
    {
168
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
169
170
            // 32 bits for "time_low"
171
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
172
173
            // 16 bits for "time_mid"
174
            mt_rand(0, 0xffff),
175
176
            // 16 bits for "time_hi_and_version",
177
            // four most significant bits holds version number 4
178
            mt_rand(0, 0x0fff) | 0x4000,
179
180
            // 16 bits, 8 bits for "clk_seq_hi_res",
181
            // 8 bits for "clk_seq_low",
182
            // two most significant bits holds zero and one for variant DCE1.1
183
            mt_rand(0, 0x3fff) | 0x8000,
184
185
            // 48 bits for "node"
186
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
187
        );
188
    }
189
}
190