服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Mssql|Oracle|Redis|

服务器之家 - 数据库 - Redis - Redis上实现分布式锁以提高性能的方案研究

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

2019-10-27 16:38Kelly Redis

这篇文章主要介绍了Redis上实现分布式锁以提高性能的方案研究,其中重点需要理解异步算法与锁的自动释放,需要的朋友可以参考下

背景:

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

项目实践

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

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

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

2、锁的生存时间默认比较短(秒级, 具体见 lock 方法), 因此若需要长时间加锁, 可以通过 expire 方法延长锁的生存时间为适当的时间. 比如在循环内调用 expire

3、系统级的锁当进程无论因为任何原因出现crash,操作系统会自己回收锁,所以不会出现资源丢失。

4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程 crash 或其他异常导致 unlock 未被调用,则该锁在剩下的时间就变成了垃圾锁,导致其他进程或进程重启后无法进入加锁区域。

  1. <?php 
  2.    
  3. require_once 'RedisFactory.php'
  4.    
  5. /** 
  6. * 在 Redis 上实现的分布式锁 
  7. */ 
  8. class RedisLock { 
  9.     
  10. //单例模式 
  11.   private static $_instance = null
  12.   public static function instance() { 
  13.     if(self::$_instance == null) { 
  14.       self::$_instance = new RedisLock(); 
  15.     } 
  16.     return self::$_instance; 
  17.   } 
  18.    
  19.     
  20. //redis对象变量 
  21.   private $redis; 
  22.     
  23. //存放被锁的标志名的数组 
  24.   private $lockedNames = array(); 
  25.    
  26.   public function __construct() { 
  27.       
  28. //获取一个 RedisString 实例 
  29.     $this->redis = RedisFactory::instance()->getString(); 
  30.   } 
  31.    
  32.     
  33. /**  
  34.     
  35. * 加锁 
  36.     
  37. * 
  38.     
  39. * @param string 锁的标识名 
  40.     
  41. * @param int 获取锁失败时的等待超时时间(秒), 在此时间之内会一直尝试获取锁直到超时. 为 0 表示失败后直接返回不等待 
  42.     
  43. * @param int 当前锁的最大生存时间(秒), 必须大于 0 . 如果超过生存时间后锁仍未被释放, 则系统会自动将其强制释放 
  44.     
  45. * @param int 获取锁失败后挂起再试的时间间隔(微秒) 
  46.     
  47. */ 
  48.   public function lock($name, $timeout = 0, $expire = 15, $waitIntervalUs = 100000) { 
  49.     if(empty($name)) return false
  50.    
  51.     $timeout = (int)$timeout; 
  52.     $expire = max((int)$expire, 5); 
  53.     $now = microtime(true); 
  54.     $timeoutAt = $now + $timeout; 
  55.     $expireAt = $now + $expire; 
  56.    
  57.     $redisKey = "Lock:$name"
  58.     while(true) { 
  59.       $result = $this->redis->setnx($redisKey, (string)$expireAt); 
  60.       if($result !== false) { 
  61.           
  62. //对$redisKey设置生存时间 
  63.         $this->redis->expire($redisKey, $expire); 
  64.           
  65. //将最大生存时刻记录在一个数组里面 
  66.         $this->lockedNames[$name] = $expireAt; 
  67.         return true
  68.       } 
  69.    
  70.         
  71. //以秒为单位,返回$redisKey 的剩余生存时间 
  72.       $ttl = $this->redis->ttl($redisKey); 
  73.         
  74. // TTL 小于 0 表示 key 上没有设置生存时间(key 不会不存在, 因为前面 setnx 会自动创建) 
  75.         
  76. // 如果出现这种情况, 那就是进程在某个实例 setnx 成功后 crash 导致紧跟着的 expire 没有被调用. 这时可以直接设置 expire 并把锁纳为己用 
  77.       if($ttl < 0) { 
  78.         $this->redis->set($redisKey, (string)$expireAt, $expire); 
  79.         $this->lockedNames[$name] = $expireAt; 
  80.         return true
  81.       } 
  82.    
  83.         
  84. // 设置了不等待或者已超时 
  85.       if($timeout <= 0 || microtime(true) > $timeoutAt) break
  86.    
  87.         
  88. // 挂起一段时间再试 
  89.       usleep($waitIntervalUs); 
  90.     } 
  91.    
  92.     return false
  93.   } 
  94.    
  95.     
  96. /** 
  97.     
  98. * 给当前锁增加指定的生存时间(秒), 必须大于 0 
  99.     
  100. * 
  101.     
  102. * @param string 锁的标识名 
  103.     
  104. * @param int 生存时间(秒), 必须大于 0 
  105.     
  106. */ 
  107.   public function expire($name, $expire) { 
  108.     if($this->isLocking($name)) { 
  109.       if($this->redis->expire("Lock:$name", max($expire, 1))) { 
  110.         return true
  111.       } 
  112.     } 
  113.     return false
  114.   } 
  115.    
  116.     
  117. /** 
  118.     
  119. * 判断当前是否拥有指定名称的锁 
  120.     
  121. * 
  122.     
  123. * @param mixed $name 
  124.     
  125. */ 
  126.   public function isLocking($name) { 
  127.     if(isset($this->lockedNames[$name])) { 
  128.       return (string)$this->lockedNames[$name] == (string)$this->redis->get("Lock:$name"); 
  129.     } 
  130.     return false
  131.   } 
  132.    
  133.     
  134. /** 
  135.     
  136. * 释放锁 
  137.     
  138. * 
  139.     
  140. * @param string 锁的标识名 
  141.     
  142. */ 
  143.   public function unlock($name) { 
  144.     if($this->isLocking($name)) { 
  145.       if($this->redis->deleteKey("Lock:$name")) { 
  146.         unset($this->lockedNames[$name]); 
  147.         return true
  148.       } 
  149.     } 
  150.     return false
  151.   } 
  152.    
  153.     
  154. /** 释放当前已经获取到的所有锁 */ 
  155.   public function unlockAll() { 
  156.     $allSuccess = true
  157.     foreach($this->lockedNames as $name => $item) { 
  158.       if(false === $this->unlock($name)) { 
  159.         $allSuccess = false
  160.       } 
  161.     } 
  162.     return $allSuccess; 
  163.   } 

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

延伸 · 阅读

精彩推荐
  • RedisRedis教程(六):Sorted-Sets数据类型

    Redis教程(六):Sorted-Sets数据类型

    这篇文章主要介绍了Redis教程(六):Sorted-Sets数据类型,本文讲解了Sorted-Sets数据类型概述、相关命令列表、命令使用示例、应用范围等内容,需要的朋友可以参...

    Redis教程网1802019-10-24
  • Redis图文详解Windows下使用Redis缓存工具的方法

    图文详解Windows下使用Redis缓存工具的方法

    这篇文章以图文结合的方式详解Windows下使用Redis缓存工具的方法,感兴趣的小伙伴们可以参考一下 ...

    daliu_it2142019-10-27
  • RedisRedis教程(二):String数据类型

    Redis教程(二):String数据类型

    这篇文章主要介绍了Redis教程(二):String数据类型,本文讲解了String数据类型概述、相关命令列表、命令使用示例三部分内容,需要的朋友可以参考下 ...

    Redis教程网4672019-10-23
  • RedisCentOS Linux系统下安装Redis过程和配置参数说明

    CentOS Linux系统下安装Redis过程和配置参数说明

    这篇文章主要介绍了CentOS Linux系统下安装Redis过程和配置参数说明,需要的朋友可以参考下 ...

    junjie4382019-10-21
  • Redis简介Redis中的showlog功能

    简介Redis中的showlog功能

    这篇文章主要介绍了简介Redis中的showlog功能,作者同时对比了DEL命令的性能,需要的朋友可以参考下 ...

    goldensun4252019-10-25
  • RedisRedis教程(八):事务详解

    Redis教程(八):事务详解

    这篇文章主要介绍了Redis教程(八):事务详解,本文讲解了,本文讲解了事务概述、相关命令列表、命令使用示例、WATCH命令和基于CAS的乐观锁等内容,需要的朋...

    Redis教程网2142019-10-24
  • RedisRedis教程(十五):C语言连接操作代码实例

    Redis教程(十五):C语言连接操作代码实例

    这篇文章主要介绍了Redis教程(十五):C语言连接操作代码实例,本篇博客是该系列博客中的最后一篇,在这里将给出基于Redis客户端组件访问并操作Redis服务器...

    Redis教程网1182019-10-24
  • RedisRedis和Memcached的区别详解

    Redis和Memcached的区别详解

    这篇文章主要介绍了Redis和Memcached的区别详解,本文从各方面总结了两个数据库的不同之处,需要的朋友可以参考下 ...

    Redis教程网1222019-10-22
北京塞车全天计划精准版 股票配资平台·选杨方配资 陕西11选5视频 恒牛所 广西11选5官网开奖 pk10直播ds 29选7 体彩中奖号码走势图 股票配资!配资658 河北11选5所有组合 快3开奖结果湖北省 黑龙江十一选五遗漏一定 江苏七位数开奖号码 什么是股票指数它有什么作用_手机新浪网 号码走势图新疆25选7 深圳股票融资 云南11选5胆拖 福利彩3d开奖结果