Redis上实现分布式锁以提高性能的方案研究

 更新时间:2015年12月07日 16:16:33   作者:Kelly  
这篇文章主要介绍了Redis上实现分布式锁以提高性能的方案研究,其中重点需要理解异步算法与锁的自动释放,需要的朋友可以参考下

背景:

在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分是解决方案基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。

项目实践

任务队列用到分布式锁的情况比较多,在将业务逻辑中可以异步处理的操作放入队列,在其他线程中处理后出队,此时队列中使用了分布式锁,保证入队和出队的一致性。关于redis队列这块的逻辑分析,我将在下一次对其进行总结,此处先略过。


接下来对redis实现的分布式锁的逻辑代码进行详细的分析和理解:

1、为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间(通过 lock 方法的参数设置或者使用默认值), 超出生存时间锁将被自动释放.

2、锁的生存时间默认比较短(秒级, 具体见 lock 方法), 因此若需要长时间加锁, 可以通过 expire 方法延长锁的生存时间为适当的时间. 比如在循环内调用 expire
3、系统级的锁当进程无论因为任何原因出现crash,操作系统会自己回收锁,所以不会出现资源丢失。
4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程 crash 或其他异常导致 unlock 未被调用,则该锁在剩下的时间就变成了垃圾锁,导致其他进程或进程重启后无法进入加锁区域。

<?php
 
require_once 'RedisFactory.php';
 
/**
* 在 Redis 上实现的分布式锁
*/
class RedisLock {
  
//单例模式
  private static $_instance = null;
  public static function instance() {
    if(self::$_instance == null) {
      self::$_instance = new RedisLock();
    }
    return self::$_instance;
  }
 
  
//redis对象变量
  private $redis;
  
//存放被锁的标志名的数组
  private $lockedNames = array();
 
  public function __construct() {
    
//获取一个 RedisString 实例
    $this->redis = RedisFactory::instance()->getString();
  }
 
  
/** 
  
* 加锁
  
*
  
* @param string 锁的标识名
  
* @param int 获取锁失败时的等待超时时间(秒), 在此时间之内会一直尝试获取锁直到超时. 为 0 表示失败后直接返回不等待
  
* @param int 当前锁的最大生存时间(秒), 必须大于 0 . 如果超过生存时间后锁仍未被释放, 则系统会自动将其强制释放
  
* @param int 获取锁失败后挂起再试的时间间隔(微秒)
  
*/
  public function lock($name, $timeout = 0, $expire = 15, $waitIntervalUs = 100000) {
    if(empty($name)) return false;
 
    $timeout = (int)$timeout;
    $expire = max((int)$expire, 5);
    $now = microtime(true);
    $timeoutAt = $now + $timeout;
    $expireAt = $now + $expire;
 
    $redisKey = "Lock:$name";
    while(true) {
      $result = $this->redis->setnx($redisKey, (string)$expireAt);
      if($result !== false) {
        
//对$redisKey设置生存时间
        $this->redis->expire($redisKey, $expire);
        
//将最大生存时刻记录在一个数组里面
        $this->lockedNames[$name] = $expireAt;
        return true;
      }
 
      
//以秒为单位,返回$redisKey 的剩余生存时间
      $ttl = $this->redis->ttl($redisKey);
      
// TTL 小于 0 表示 key 上没有设置生存时间(key 不会不存在, 因为前面 setnx 会自动创建)
      
// 如果出现这种情况, 那就是进程在某个实例 setnx 成功后 crash 导致紧跟着的 expire 没有被调用. 这时可以直接设置 expire 并把锁纳为己用
      if($ttl < 0) {
        $this->redis->set($redisKey, (string)$expireAt, $expire);
        $this->lockedNames[$name] = $expireAt;
        return true;
      }
 
      
// 设置了不等待或者已超时
      if($timeout <= 0 || microtime(true) > $timeoutAt) break;
 
      
// 挂起一段时间再试
      usleep($waitIntervalUs);
    }
 
    return false;
  }
 
  
/**
  
* 给当前锁增加指定的生存时间(秒), 必须大于 0
  
*
  
* @param string 锁的标识名
  
* @param int 生存时间(秒), 必须大于 0
  
*/
  public function expire($name, $expire) {
    if($this->isLocking($name)) {
      if($this->redis->expire("Lock:$name", max($expire, 1))) {
        return true;
      }
    }
    return false;
  }
 
  
/**
  
* 判断当前是否拥有指定名称的锁
  
*
  
* @param mixed $name
  
*/
  public function isLocking($name) {
    if(isset($this->lockedNames[$name])) {
      return (string)$this->lockedNames[$name] == (string)$this->redis->get("Lock:$name");
    }
    return false;
  }
 
  
/**
  
* 释放锁
  
*
  
* @param string 锁的标识名
  
*/
  public function unlock($name) {
    if($this->isLocking($name)) {
      if($this->redis->deleteKey("Lock:$name")) {
        unset($this->lockedNames[$name]);
        return true;
      }
    }
    return false;
  }
 
  
/** 释放当前已经获取到的所有锁 */
  public function unlockAll() {
    $allSuccess = true;
    foreach($this->lockedNames as $name => $item) {
      if(false === $this->unlock($name)) {
        $allSuccess = false;
      }
    }
    return $allSuccess;
  }
}

此类很多代码都写上了注释,只要认真理解下,就很容易懂得如何在redis实现分布式锁了。

相关文章

  • Redis 中spark参数executor-cores引起的异常解决办法

    Redis 中spark参数executor-cores引起的异常解决办法

    这篇文章主要介绍了Redis 中spark参数executor-cores引起的异常解决办法的相关资料,需要的朋友可以参考下
    2017-03-03
  • Spring Boot 3.0x的Redis 分布式锁的概念和原理

    Spring Boot 3.0x的Redis 分布式锁的概念和原理

    Redis 分布式锁是一种基于 Redis 的分布式锁解决方案,它的原理是利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性,这篇文章主要介绍了适合 Spring Boot 3.0x的Redis 分布式锁,需要的朋友可以参考下
    2024-08-08
  • Redis自动化安装及集群实现搭建过程

    Redis自动化安装及集群实现搭建过程

    这篇文章主要介绍了Redis自动化安装以及集群实现搭建过程,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • Redis的Expire与Setex区别说明

    Redis的Expire与Setex区别说明

    这篇文章主要介绍了Redis的Expire与Setex区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • 基于Redis的限流器的实现(示例讲解)

    基于Redis的限流器的实现(示例讲解)

    下面小编就为大家分享一篇基于Redis的限流器的实现(示例讲解),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • Redis锁完美解决高并发秒杀问题

    Redis锁完美解决高并发秒杀问题

    本文主要介绍了Redis锁完美解决高并发秒杀问题,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 详解在Redis在Centos7上的安装部署

    详解在Redis在Centos7上的安装部署

    Redis是一种高级key-value数据库。这篇文章主要介绍了详解在Redis在Centos7上的安装部署,有兴趣的可以了解一下。
    2016-12-12
  • 使用Redis有序集合实现IP归属地查询详解

    使用Redis有序集合实现IP归属地查询详解

    这篇文章主要介绍了使用Redis有序集合实现IP归属地查询,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • Redis系列之底层数据结构SDS详解

    Redis系列之底层数据结构SDS详解

    SDS(简单动态字符串)是Redis使用的核心数据结构,用于替代C语言的字符串,以解决长度获取慢、内存溢出等问题,SDS通过预分配与惰性释放策略优化内存使用,增强安全性,且能存储文本与二进制数据,可查看源码src/sds.h和src/sds.c了解更多
    2024-11-11
  • Window下对Redis进行开启与关闭的操作方法

    Window下对Redis进行开启与关闭的操作方法

    这篇文章主要介绍了Window下对Redis进行开启与关闭的操作方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-11-11

最新评论