本文将会分为三个版本:初级版、进阶版、近乎完美版来介绍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);
}