Redis实现分布式锁的正确姿势

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/William0318/article/details/89202592

问题

在服务端的开发中,经常会遇到并发问题,并发问题大家能想到的就是并行变串行,锁就是一种并行变串行的方式,redis就可以利用其单线程的特性实现分布式锁。
锁的特性:一个进程在getLock 处理业务逻辑,没有releaseLock前,别的进程请求getLock只能等待,也就是说同一时刻只能有一个进程握有锁。

如果逻辑执行时间长,则需要超时进行释放锁。

1、利用Redis中setnx可以实现简单的分布式锁

//加锁操作
public function lock($key, $expire = 5)
{
     if($this->redis->setnx($key, 1)){   
        $this->redis->expire($key, $expire); //过期时间为了防止发生异常锁永远得不到释放的情况
        return true; //加锁成功
     }else{
        return false; //加锁失败
     }
}
//解锁操作
public function unLock($key)
{
   return $this->redis->del($key);  
}

存在问题:如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。
这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。

2、redis 2.8 中 作者丰富了set指令参数,set和过期时间设置柔和为一个原子操作

//加锁操作
public function lock($key, $expire = 5)
{
     if($this->redis->set($key, 1, "EX", $expire, "NX")){        
        return true; //加锁成功
     }else{
        return false; //加锁失败
     }
}
//解锁操作
public function unLock($key)
{
   return $this->redis->del($key);  
}

存在问题: 如果一个进程的逻辑执行过长导致锁超时,别的进程这时候拿到锁进行业务逻辑处理,第一个进程去释放锁,导致会把第二个进程的锁释放,起不到锁的作用。优化思路,加入随机数,只释放自己进程的锁。

//加锁操作
static::$lockerMap;
public function lock($key, $expire = 5)
{
     $value = mt_rand(1, 1000000); 
     if($this->redis->set($key, $value, "EX", $expire, "NX")){        
        static::$lockerMap[$key] = $value;
        return true; //加锁成功
     }else{
        return false; //加锁失败
     }
}
//解锁操作
public function unLock($key)
{
   if(isset(static::$lockerMap[$key]) && $this->redis->get($key) == static::$lockerMap[$key]){
   unset(static::$lockerMap[$key]); 
   return $this->redis->del($key);  
}

存在问题:如果一个进程持有锁,另外一个进程请求锁直接失败,导致业务处理失败;此时可以对加锁操作进行循环检测,让进程等待而不是直接失败,优化后的加锁程序如下:

//加锁操作
static::$lockerMap;
public function lock($key, $expire = 5)
{
     $value = mt_rand(1, 1000000);
     $waitTime = 0;
     while($waitTime < $expire && false == $this->redis->set($key, $value, "EX", $expire, "NX"))
     {
          sleep(1);
          $waitTime += 1;
     }      
     if($waitTime >= $expire){
         return false; //加锁失败
     }
     static::$lockerMap[$key] = $value;
     return true; //加锁成功
}

3、锁的可重入性

锁的可重入性指一个进程是否对同一个key多次加锁,可通过维护本地的计数实现。不推荐锁可重入,这将导致维护复杂,而且程序设计完全可以避开。

//加锁操作
static::$lockerMap;
static::$lockerCnt;
public function lock($key, $expire = 5)
{
     $value = mt_rand(1, 1000000);
     $waitTime = 0;
     if(isset(static::$lockerCnt[$key])){
          static::$lockerCnt[$key] += 1;
          return true;
     }
     while($waitTime < $expire && false == $this->redis->set($key, $value, "EX", $expire, "NX"))
     {
          sleep(1);
          $waitTime += 1;
     }      
     if($waitTime >= $expire){
         return false; //加锁失败
     }
     static::$lockerCnt[$key] += 1;
     static::$lockerMap[$key] = $value;
     return true; //加锁成功
}
//解锁操作
public function unLock($key)
{
   if(false == isset(static::$lockerMap[$key]){
       return false;
   } 
   static::$lockerCnt[$key] -= 1;
   if(static::$lockerCnt[$key] == 0)
   {
       if(isset(static::$lockerMap[$key]) && $this->redis->get($key) == static::$lockerMap[$key]){
       unset(static::$lockerMap[$key]); 
       return $this->redis->del($key);  
   }
   return true;
}

以上就是Redis锁的部分内容,如果有瑕疵,可在下方评论区域评论,欢迎批评和指正。

猜你喜欢

转载自blog.csdn.net/William0318/article/details/89202592
今日推荐