【注意】最后更新于 December 11, 2020,文中内容可能已过时,请谨慎使用。
本文将会分为三个版本:初级版、进阶版、近乎完美版
来介绍redis分布式锁
进阶的方向主要是在一致性上进行了优化,最后一版可以说是高度一致性!
初级版
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
|
/**
* 加锁 新版锁 标注 在解锁时必须判断是否获取到了锁才能解锁
* @param [type] $name 锁的标识名
* @param integer $timeout 循环获取锁的等待超时时间,在此时间内会一直尝试获取锁直到超时,为0表示失败后直接返回不等待
* @param integer $expire 当前锁的最大生存时间(秒),必须大于0,如果超过生存时间锁仍未被释放,则系统会自动强制释放 默认是30s
* @param integer $waitIntervalUs 获取锁失败后挂起再试的时间间隔(微秒)
* @return [type] [description]
*/
public function lock($name, $timeout = 0.5, $expire = 20, $waitIntervalUs = 100000)
{
if ($name == null) return false;
//取得当前时间
$now = time();
//获取锁失败时的等待超时时刻
$timeoutAt = $now + $timeout;
//锁的最大生存时刻
$expireAt = $now + $expire;
$redisKey = $this -> lockName($name);
while (true) {
//将rediskey的最大生存时刻存到redis里,过了这个时刻该锁会被自动释放
$result = $this->__redis->setNx($redisKey, $expireAt);
if ($result != false)
{
//设置key的失效时间
$this->__redis->expire($redisKey, $expire);
//将锁标志放到lockedNames数组里
$this->lockedNames[$name] = $expireAt;
return true;
}
/*****循环请求锁部分*****/
//如果没设置锁失败的等待时间 或者 已超过最大等待时间了,那就退出
if ($timeout <= 0 || $timeoutAt < microtime(true)) break;
//隔 $waitIntervalUs 后继续 请求
usleep($waitIntervalUs);
}
return false;
}
/**
* 解锁
* @param [type] $name [description]
* @return [type] [description]
*/
public function unlock($name)
{
//先看lonkedName[$name]是否存在该锁标志名
$expireTime = $this->__redis->ttl($this -> lockName($name));
if ($expireTime >= 0)
{
//说明没过期
//删除锁
if ($this->__redis->rm($this -> lockName($name)))
{
//清掉lockedNames里的锁标志
return true;
}
}
//说明过期了
return true;
}
|
进阶版
将setNx+expire替换成Redis::set(“my:lock”, $token, “ex”, “5”, “nx”);
但删锁的时候还是两步操作,无法保证一致性,于是便有了下面的近乎完美版
近乎完美版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
while (true)
{
if(redisLock($token))
{
//业务内容
unlock($token);
break;
}
}
function redisLock($token) {
return Redis::set("my:lock", $token, "ex", "5", "nx");
}
function unlock($token) {
$script = 'if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end ';
return Redis::eval($script,1,'my:lock',$token);
}
|