set file:9527 ${random_value} NX EX ${timeout}
1 if redis.call("get",KEYS[1]) == ARGV[1] then
2 return redis.call("del",KEYS[1])
3 else
4 return 0
5 end
事实上这类琐最大的缺点就是它加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:
正因为如此,Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock。笔者认为,Redlock也是Redis所有分布式锁实现方式中唯一能让面试官高潮的方式。
在主从架构下,之前那套分布式锁的机制,就已经失效了,原因正如之前说的:
如果A往Master放入了一把锁,然后再数据同步到Slave之前,Master crash,Slave被提拔为Master,这时候Master上面就没有锁了,这样其他进程也可以拿到锁,违法了锁的互斥性。
针对Redis集群架构,redis的作者antirez提出了Redlock算法,来实现集群架构下的分布式锁。
在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。现在我们假设有5个Redis master节点,同时我们需要在5台服务器上面运行这些Redis实例,这样保证他们不会同时都宕掉。
为了取到锁,客户端应该执行以下操作:
按照A->B->C的顺序,依次向这三台Master发送获取锁的命令。客户端在等待每台Master回响应时,都有超时时间timeout。 举个例子,客户端向A发送获取锁的命令,在等了timeout时间之后,都没收到响应,就会认为获取锁失败,继续尝试获取下一把锁
1class RedisLock
2{
3 /**
4 * 获取锁
5 * @param string $lock_name 锁名
6 * @param int $acquire_time 重复请求次数
7 * @param int $lock_timeout 请求超时时间
8 * @return bool|string
9 */
10 public static function acquireLock($lock_name, $acquire_time = 3, $lock_timeout = 20)
11 {
12 $identifier = md5($_SERVER['REQUEST_TIME'] . mt_rand(1, 10000000));
13 $lock_name = 'AGENT_ORDER_LOCK:' . $lock_name;
14 $lock_timeout = intval(ceil($lock_timeout));
15 $end_time = time() + $acquire_time;
16 while (time() < $end_time) {
17 $script = <<<luascript
18 local result = redis.call('setnx',KEYS[1],ARGV[1]);
19 if result == 1 then
20 redis.call('expire',KEYS[1],ARGV[2])
21 return 1
22 elseif redis.call('ttl',KEYS[1]) == -1 then
23 redis.call('expire',KEYS[1],ARGV[2])
24 return 0
25 end
26 return 0
27luascript;
28 $result = location_redis()->evaluate($script, array($lock_name, $identifier, $lock_timeout), 1);
29 if ($result == '1') {
30 return $identifier;
31 }
32 // 锁饥饿的问题。获取锁失败后,随机休息一段时间
33 usleep(100000); // 函数延迟代码执行若干微秒
34 }
35 return false;
36 }
37
38 /**
39 * 释放锁
40 * @param string $lock_name 锁名
41 * @param string $identifier 获取锁返回的标识
42 * @return bool
43 */
44 public static function releaseLock($lock_name, $identifier)
45 {
46 $lock_name = 'AGENT_ORDER_LOCK:' . $lock_name;
47 while (true) {
48 $script = <<<luascript
49 local result = redis.call('get',KEYS[1]);
50 if result == ARGV[1] then
51 if redis.call('del',KEYS[1]) == 1 then
52 return 1;
53 end
54 end
55 return 0
56luascript;
57 $result = location_redis()->evaluate($script, array($lock_name, $identifier), 1);
58 if ($result == 1) {
59 return true;
60 }
61 break;
62 }
63 //进程已经失去了锁
64 return false;
65 }
66
67 /**
68 * 添加黑白名单
69 * @param string $ip IP地址
70 * @param bool $isWhite 是白名单,否则为黑
71 * @return int
72 */
73 public function addBlackWhiteList($ip, $isWhite = true)
74 {
75 if($isWhite){
76 $result = location_redis()->sAdd('IP_WHITE_LIST:AGENT',$ip);
77 }else{
78 $result = location_redis()->sAdd('IP_BLACK_LIST:AGENT',$ip);
79 }
80 return $result;
81 }
82
83 /**
84 * 获取远程服务端IP
85 * @return array|false|null|string
86 */
87 public static function getRemoteIp()
88 {
89 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
90 $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
91 } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
92 $ip = $_SERVER['HTTP_CLIENT_IP'];
93 } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
94 $ip = getenv('HTTP_X_FORWARED_FOR');
95 } elseif (getenv('HTTP_CLIENT_IP')) {
96 $ip = getenv('HTTP_CLIENT_IP');
97 } elseif ($_SERVER['REMOTE_ADDR']) {
98 $ip = $_SERVER['REMOTE_ADDR'];
99 } else {
100 $ip = null;
101 }
102 Log::debug('[获取用户的IP] '.$ip);
103 return $ip;
104 }
105}